package org.edx.mobile.http; import android.content.Context; import android.os.Bundle; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import com.google.inject.Inject; import com.google.inject.Singleton; import org.edx.mobile.authentication.AuthResponse; import org.edx.mobile.exception.AuthException; import org.edx.mobile.http.cache.CacheManager; import org.edx.mobile.interfaces.SectionItemInterface; import org.edx.mobile.logger.Logger; import org.edx.mobile.model.api.AnnouncementsModel; import org.edx.mobile.model.api.AuthErrorResponse; import org.edx.mobile.model.api.ChapterModel; import org.edx.mobile.model.api.CourseInfoModel; import org.edx.mobile.model.api.EnrolledCoursesResponse; import org.edx.mobile.model.api.FormFieldMessageBody; import org.edx.mobile.model.api.HandoutModel; import org.edx.mobile.model.api.ProfileModel; import org.edx.mobile.model.api.RegisterResponse; import org.edx.mobile.model.api.ResetPasswordResponse; import org.edx.mobile.model.api.SectionEntry; import org.edx.mobile.model.api.SectionItemModel; import org.edx.mobile.model.api.SyncLastAccessedSubsectionResponse; import org.edx.mobile.model.api.VideoResponseModel; import org.edx.mobile.module.prefs.LoginPrefs; import org.edx.mobile.module.registration.model.RegistrationDescription; import org.edx.mobile.user.UserAPI; import org.edx.mobile.util.Config; import org.edx.mobile.util.DateUtil; import org.edx.mobile.util.NetworkUtil; import org.json.JSONObject; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpCookie; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @Singleton @Deprecated // Deprecated because this uses org.apache.http, which is itself deprecated public class Api implements IApi { @Inject Config config; @Inject private HttpManager http; @Inject private CacheManager cache; @Inject private UserAPI userApi; @Inject private LoginPrefs loginPrefs; private Context context; protected final Logger logger = new Logger(getClass().getName()); @Inject public Api(Context context) { this.context = context; } /** * Returns entire course hierarchy. * * @param courseId * @param preferCache * @return * @throws Exception */ @Deprecated public Map<String, SectionEntry> getCourseHierarchy(String courseId, boolean preferCache) throws Exception { Bundle p = new Bundle(); p.putString("format", "json"); String url = getBaseUrl() + "/api/mobile/v0.5/video_outlines/courses/" + courseId; logger.debug("Get course heirarchy url - " + url); String json = null; if (NetworkUtil.isConnected(context) && !preferCache) { // get data from server String urlWithAppendedParams = HttpManager.toGetUrl(url, p); json = http.get(urlWithAppendedParams, getAuthHeaders()).body; // cache the response cache.put(url, json); } else { json = cache.get(url); } if (json == null) { return null; } //Initializing task call logger.debug("course_hierarchy= " + json); Gson gson = new GsonBuilder().create(); TypeToken<ArrayList<VideoResponseModel>> t = new TypeToken<ArrayList<VideoResponseModel>>() { }; ArrayList<VideoResponseModel> list = gson.fromJson(json, t.getType()); // create hierarchy with chapters, sections and subsections // HashMap<String, SectionEntry> chapterMap = new HashMap<String, SectionEntry>(); Map<String, SectionEntry> chapterMap = new LinkedHashMap<String, SectionEntry>(); for (VideoResponseModel m : list) { // add each video to its corresponding chapter and section // add this as a chapter String cname = m.getChapter().getDisplayName(); // carry this courseId with video model m.setCourseId(courseId); SectionEntry s = null; if (chapterMap.containsKey(cname)) { s = chapterMap.get(cname); } else { s = new SectionEntry(); s.chapter = cname; s.isChapter = true; s.section_url = m.getSectionUrl(); chapterMap.put(cname, s); } // add this video to section inside in this chapter ArrayList<VideoResponseModel> videos = s.sections.get(m.getSection().getDisplayName()); if (videos == null) { s.sections.put(m.getSection().getDisplayName(), new ArrayList<VideoResponseModel>()); videos = s.sections.get(m.getSection().getDisplayName()); } //This has been commented out because for some Videos thereare no english srt's and hence returning empty /*try{ // FIXME: Utter hack code that should be removed as soon as the server starts // returning default english transcripts. if (m.getSummary().getTranscripts().englishUrl == null) { // Example URL: "http://mobile3.m.sandbox.edx.org/api/mobile/v0.5/video_outlines/transcripts/MITx/6.002x_4x/3T2014/Welcome/en"; String usageKeyParts[] = m.getSummary().getId().split("/"); String usageKey = usageKeyParts[usageKeyParts.length - 1]; String fallbackUrl = getBaseUrl() + "/api/mobile/v0.5/video_outlines/transcripts/" + courseId + "/" + usageKey + "/en"; m.getSummary().getTranscripts().englishUrl = fallbackUrl; } }catch(Exception e){ logger.error(e); }*/ videos.add(m); } return chapterMap; } /** * Returns video model for given course id and video id. * * @param courseId * @param videoId * @return * @throws Exception */ @Deprecated public VideoResponseModel getVideoById(String courseId, String videoId) throws Exception { Map<String, SectionEntry> map = getCourseHierarchy(courseId, true); // iterate chapters for (Entry<String, SectionEntry> chapterentry : map.entrySet()) { // iterate lectures for (Entry<String, ArrayList<VideoResponseModel>> entry : chapterentry.getValue().sections.entrySet()) { // iterate videos for (VideoResponseModel v : entry.getValue()) { // identify the video if (videoId.equals(v.getSummary().getId())) { return v; } } } } return null; } /** * Returns enrolled courses of given user. * * @return * @throws Exception */ @Override public List<EnrolledCoursesResponse> getEnrolledCourses() throws Exception { return getEnrolledCourses(false); } /** * Returns course identified by given id from cache, null if not course is found. * * @param courseId * @return */ @Override public EnrolledCoursesResponse getCourseById(String courseId) { try { for (EnrolledCoursesResponse r : getEnrolledCourses(true)) { if (r.getCourse().getId().equals(courseId)) { return r; } } } catch (Exception ex) { logger.error(ex); } return null; } /** * Returns enrolled courses of given user. * * @param fetchFromCache * @return * @throws Exception */ @Override public List<EnrolledCoursesResponse> getEnrolledCourses(boolean fetchFromCache) throws Exception { Bundle p = new Bundle(); p.putString("format", "json"); String org = config.getOrganizationCode(); if (org != null) { p.putString("org", org); } String url = userApi.getUserEnrolledCoursesURL(loginPrefs.getUsername()); String json = null; if (NetworkUtil.isConnected(context) && !fetchFromCache) { // get data from server String urlWithAppendedParams = HttpManager.toGetUrl(url, p); final HttpManager.HttpResult result = http.get(urlWithAppendedParams, getAuthHeaders()); if (result.statusCode == HttpStatus.UNAUTHORIZED) { throw new HttpAuthRequiredException(); } json = result.body; // cache the response cache.put(url, json); } if (json == null) { json = cache.get(url); } if (json == null) { return null; } logger.debug("Url " + "enrolled_courses=" + json); Gson gson = new GsonBuilder().create(); AuthErrorResponse authError = null; try { // check if auth error authError = gson.fromJson(json, AuthErrorResponse.class); } catch (Exception ex) { // nothing to do here } if (authError != null && authError.detail != null) { throw new AuthException(authError.detail); } TypeToken<ArrayList<EnrolledCoursesResponse>> t = new TypeToken<ArrayList<EnrolledCoursesResponse>>() { }; ArrayList<EnrolledCoursesResponse> list = gson.fromJson(json, t.getType()); return list; } /** * Returns list of videos in a particular course. * * @param courseId * @param preferCache * @return * @throws Exception */ @Deprecated private ArrayList<VideoResponseModel> getVideosByCourseId(String courseId, boolean preferCache) throws Exception { Bundle p = new Bundle(); String url = getBaseUrl() + "/api/mobile/v0.5/video_outlines/courses/" + courseId; String json = null; if (NetworkUtil.isConnected(context) && !preferCache) { // get data from server //Change from post to get. as post is not supported //FIXME - it does not check the return code before it cache the result. json = http.get(url, getAuthHeaders()).body; // cache the response cache.put(url, json); } else { json = cache.get(url); } logger.debug("videos_by_course=" + json); Gson gson = new GsonBuilder().create(); TypeToken<ArrayList<VideoResponseModel>> t = new TypeToken<ArrayList<VideoResponseModel>>() { }; ArrayList<VideoResponseModel> list = gson.fromJson(json, t.getType()); return list; } /** * Returns handout for the given course id. * * @param url * @return * @throws Exception */ @Override public HandoutModel getHandout(String url, boolean prefCache) throws Exception { Bundle p = new Bundle(); p.putString("format", "json"); String json = null; if (NetworkUtil.isConnected(context) || !prefCache) { // get data from server String urlWithAppendedParams = HttpManager.toGetUrl(url, p); logger.debug("Url " + urlWithAppendedParams); json = http.get(urlWithAppendedParams, getAuthHeaders()).body; // cache the response cache.put(url, json); } else { json = cache.get(url); } if (json == null) { return null; } logger.debug("handout=" + json); Gson gson = new GsonBuilder().create(); HandoutModel res = gson.fromJson(json, HandoutModel.class); return res; } /** * Returns list of announcements for the given course id. * * @param url * @param preferCache * @return * @throws Exception */ @Override public List<AnnouncementsModel> getAnnouncement(String url, boolean preferCache) throws Exception { Bundle p = new Bundle(); p.putString("format", "json"); String json = null; if (NetworkUtil.isConnected(context) && !preferCache) { // get data from server String urlWithAppendedParams = HttpManager.toGetUrl(url, p); logger.debug("url : " + urlWithAppendedParams); json = http.get(urlWithAppendedParams, getAuthHeaders()).body; // cache the response cache.put(url, json); } else { json = cache.get(url); } if (json == null) { return null; } Gson gson = new GsonBuilder().create(); TypeToken<List<AnnouncementsModel>> t = new TypeToken<List<AnnouncementsModel>>() { }; List<AnnouncementsModel> list = gson.fromJson(json, t.getType()); return list; } /** * Returns "Authorization" header with current active access token. * * @return */ private Bundle getAuthHeaders() { Bundle headers = new Bundle(); final String token = loginPrefs.getAuthorizationHeader(); headers.putString("Authorization", token); return headers; } /** * Returns Stream object from the given URL. * * @param url * @param preferCache * @return * @throws Exception */ private CourseInfoModel srtStream(String url, boolean preferCache) throws Exception { Bundle p = new Bundle(); p.putString("format", "json"); String json = null; if (NetworkUtil.isConnected(context) && !preferCache) { // get data from server String urlWithAppendedParams = HttpManager.toGetUrl(url, p); logger.debug("Url " + urlWithAppendedParams); json = http.get(urlWithAppendedParams, getAuthHeaders()).body; // cache the response //cache.put(url, json); } else { json = cache.get(url); } if (json == null) { return null; } logger.debug("srt stream= " + json); Gson gson = new GsonBuilder().create(); CourseInfoModel res = gson.fromJson(json, CourseInfoModel.class); return res; } @Override public String downloadTranscript(String url) throws Exception { if (url != null) { try { if (NetworkUtil.isConnected(this.context)) { String str = http.get(url, getAuthHeaders()).body; return str; } } catch (Exception ex) { logger.error(ex); } } return null; } /** * Returns API base URL for the current project configuration (mobile3 or production). * * @return */ public String getBaseUrl() { return config.getApiHostURL(); } /** * Returns chapter model and the subsequent sections and videos in organized manner from cache. * * @param courseId * @param chapter * @return */ @Deprecated public ArrayList<SectionItemInterface> getLiveOrganizedVideosByChapter (String courseId, String chapter) { ArrayList<SectionItemInterface> list = new ArrayList<SectionItemInterface>(); // add chapter to the result ChapterModel c = new ChapterModel(); c.name = chapter; list.add(c); try { HashMap<String, ArrayList<VideoResponseModel>> sections = new LinkedHashMap<String, ArrayList<VideoResponseModel>>(); ArrayList<VideoResponseModel> videos = getVideosByCourseId(courseId, true); for (VideoResponseModel v : videos) { // filter videos by chapter if (v.getChapter().getDisplayName().equals(chapter)) { // this video is under the specified chapter // sort out the section of this video if (sections.containsKey(v.getSection().getDisplayName())) { ArrayList<VideoResponseModel> sv = sections.get(v.getSection().getDisplayName()); if (sv == null) { sv = new ArrayList<VideoResponseModel>(); } sv.add(v); } else { ArrayList<VideoResponseModel> vlist = new ArrayList<VideoResponseModel>(); vlist.add(v); sections.put(v.getSection().getDisplayName(), vlist); } } } // now add sectioned videos to the result for (Entry<String, ArrayList<VideoResponseModel>> entry : sections.entrySet()) { // add section to the result SectionItemModel s = new SectionItemModel(); s.name = entry.getKey(); list.add(s); // add videos to the result if (entry.getValue() != null) { for (VideoResponseModel v : entry.getValue()) { list.add(v); } } } } catch (Exception e) { logger.error(e); } return list; } @Override public SyncLastAccessedSubsectionResponse syncLastAccessedSubsection(String courseId, String lastVisitedModuleId) throws Exception { final String username = loginPrefs.getUsername(); String url = getBaseUrl() + "/api/mobile/v0.5/users/" + username + "/course_status_info/" + courseId; logger.debug("PATCH url for syncLastAccessed Subsection: " + url); String date = DateUtil.getCurrentTimeStamp(); JSONObject postBody = new JSONObject(); postBody.put("last_visited_module_id", lastVisitedModuleId); postBody.put("modification_date", date); logger.debug("PATCH body for syncLastAccessed Subsection: " + postBody.toString()); String json = http.post(url, postBody.toString(), getAuthHeaders(), true); if (json == null) { return null; } logger.debug("Response of sync last viewed= " + json); Gson gson = new GsonBuilder().create(); SyncLastAccessedSubsectionResponse res = gson.fromJson(json, SyncLastAccessedSubsectionResponse.class); return res; } @Override public SyncLastAccessedSubsectionResponse getLastAccessedSubsection(String courseId) throws Exception { final String username = loginPrefs.getUsername(); String url = getBaseUrl() + "/api/mobile/v0.5/users/" + username + "/course_status_info/" + courseId; logger.debug("Url of get last accessed subsection: " + url); String json = http.get(url, getAuthHeaders()).body; if (json == null) { return null; } logger.debug("Response of get last viewed subsection.id = " + json); Gson gson = new GsonBuilder().create(); return gson.fromJson(json, SyncLastAccessedSubsectionResponse.class); } /** * Reads registration description from assets and return Model representation of it. * * @return * @throws IOException */ @Override public RegistrationDescription getRegistrationDescription() throws Exception { Gson gson = new Gson(); InputStream in = context.getAssets().open("config/registration_form.json"); RegistrationDescription form = gson.fromJson(new InputStreamReader(in), RegistrationDescription.class); logger.debug("picking up registration description (form) from assets, not from cache"); return form; } @Override public Boolean enrollInACourse(String courseId, boolean email_opt_in) throws Exception { String enrollUrl = getBaseUrl() + "/api/enrollment/v1/enrollment"; logger.debug("POST url for enrolling in a Course: " + enrollUrl); JSONObject postBody = new JSONObject(); JSONObject courseIdObject = new JSONObject(); courseIdObject.put("email_opt_in", email_opt_in); courseIdObject.put("course_id", courseId); postBody.put("course_details", courseIdObject); logger.debug("POST body for Enrolling in a course: " + postBody.toString()); String json = http.post(enrollUrl, postBody.toString(), getAuthHeaders(), false); if (json != null && !json.isEmpty()) { logger.debug("Response of Enroll in a course= " + json); JSONObject resultJson = new JSONObject(json); if (resultJson.has("error")) { return false; } else { return true; } } return false; } public String getSessionTokenExchangeUrl() { return getBaseUrl() + "/oauth2/login/"; } /** * used for assessment webview, refresh session id */ @Override public List<HttpCookie> getSessionExchangeCookie() throws Exception { return http.getCookies(getSessionTokenExchangeUrl(), getAuthHeaders(), false); } public HttpManager.HttpResult getCourseStructure(HttpRequestDelegate delegate) throws Exception { logger.debug("GET url for enrolling in a Course: " + delegate.endPoint.getUrl()); if (NetworkUtil.isConnected(context)) { // get data from server String urlWithAppendedParams = HttpManager.toGetUrl(delegate.endPoint.getUrl(), null); HttpManager.HttpResult result = http.get(urlWithAppendedParams, getAuthHeaders()); return result; } return null; } public static class HttpAuthRequiredException extends Exception { } }