From 72216ea7d6e253e15b9b3fdc1b0e372493aecf7d Mon Sep 17 00:00:00 2001 From: Sebastian Seedorf Date: Fri, 8 Feb 2019 15:17:28 +0100 Subject: [PATCH 1/3] Load module list and parts only if logged in --- .../fuplanner/services/kvv/ModulesAnnouncements.java | 8 ++------ .../sebse/fuplanner/services/kvv/ModulesAssignments.java | 8 ++------ .../de/sebse/fuplanner/services/kvv/ModulesEvents.java | 8 ++------ .../java/de/sebse/fuplanner/services/kvv/ModulesList.java | 8 ++------ .../de/sebse/fuplanner/services/kvv/ModulesResources.java | 8 ++------ 5 files changed, 10 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesAnnouncements.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesAnnouncements.java index fbeb125..3e54973 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesAnnouncements.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesAnnouncements.java @@ -39,7 +39,7 @@ public class ModulesAnnouncements extends PartModules> { errorCallback.onError(new NetworkError(101204, 500, "Currently running in offline mode!")); return; } - super.get(String.format("https://kvv.imp.fu-berlin.de/direct/announcement/site/%s.json?n=999999&d=999999999", ID), mLogin.getLoginTokenKVV().getCookies(), response -> { + super.get(String.format("https://kvv.imp.fu-berlin.de/direct/announcement/site/%s.json?n=999999&d=999999999&_validateSession=", ID), mLogin.getLoginTokenKVV().getCookies(), response -> { String body = response.getParsed(); if (body == null) { errorCallback.onError(new NetworkError(101201, 403, "No announcements retrieved!")); @@ -82,11 +82,7 @@ public class ModulesAnnouncements extends PartModules> { } } - // Empty announcements *may be* because token is invalid -> check - if (announcements.size() == 0) - mLogin.testLoginToken(token -> callback.onResponse(announcements), errorCallback); - else - callback.onResponse(announcements); + callback.onResponse(announcements); }, error -> errorCallback.onError(new NetworkError(101203, error.networkResponse.statusCode, "Cannot get announcements!"))); } diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesAssignments.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesAssignments.java index b0230d3..6a9101a 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesAssignments.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesAssignments.java @@ -39,7 +39,7 @@ public class ModulesAssignments extends PartModules { errorCallback.onError(new NetworkError(101304, 500, "Currently running in offline mode!")); return; } - get(String.format("https://kvv.imp.fu-berlin.de/direct/assignment/site/%s.json", ID), mLogin.getLoginTokenKVV().getCookies(), response -> { + get(String.format("https://kvv.imp.fu-berlin.de/direct/assignment/site/%s.json?_validateSession=", ID), mLogin.getLoginTokenKVV().getCookies(), response -> { String body = response.getParsed(); if (body == null) { errorCallback.onError(new NetworkError(101301, 403, "No assignments retrieved!")); @@ -80,11 +80,7 @@ public class ModulesAssignments extends PartModules { } } - // Empty assignments *may be* because token is invalid -> check - if (assignments.size() == 0) - mLogin.testLoginToken(token -> callback.onResponse(assignments), errorCallback); - else - callback.onResponse(assignments); + callback.onResponse(assignments); }, error -> errorCallback.onError(new NetworkError(101303, error.networkResponse.statusCode, "Cannot get assignments!"))); } diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesEvents.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesEvents.java index eee559b..60583f4 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesEvents.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesEvents.java @@ -45,7 +45,7 @@ public class ModulesEvents extends PartModules { errorCallback.onError(new NetworkError(101404, 500, "Currently running in offline mode!")); return; } - get(String.format("https://kvv.imp.fu-berlin.de/direct/calendar/site/%s.json?detailed=true", ID), mLogin.getLoginTokenKVV().getCookies(), response -> { + get(String.format("https://kvv.imp.fu-berlin.de/direct/calendar/site/%s.json?detailed=true&_validateSession=", ID), mLogin.getLoginTokenKVV().getCookies(), response -> { String body = response.getParsed(); if (body == null) { errorCallback.onError(new NetworkError(101401, 403, "No events retrieved!")); @@ -81,11 +81,7 @@ public class ModulesEvents extends PartModules { } } - // Empty events *may be* because token is invalid -> check - if (events.size() == 0) - mLogin.testLoginToken(token -> callback.onResponse(events), errorCallback); - else - callback.onResponse(events); + callback.onResponse(events); }, error -> errorCallback.onError(new NetworkError(101403, error.networkResponse.statusCode, "Cannot get events!"))); } diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java index 09bd2fb..767cbc1 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java @@ -175,7 +175,7 @@ public class ModulesList extends HTTPService { callback.onResponse(new Modules(mLogin.getLoginTokenKVV().getUsername())); return; } - get("https://kvv.imp.fu-berlin.de/direct/site.json", mLogin.getLoginTokenKVV().getCookies(), response -> { + get("https://kvv.imp.fu-berlin.de/direct/site.json?_validateSession=", mLogin.getLoginTokenKVV().getCookies(), response -> { String body = response.getParsed(); if (body == null) { errorCallback.onError(new NetworkError(101111, 403, "No module list retrieved!")); @@ -228,11 +228,7 @@ public class ModulesList extends HTTPService { e.printStackTrace(); } } - // Empty module *may be* because token is invalid -> check - if (modules.size() == 0) - mLogin.testLoginToken(token -> callback.onResponse(modules), errorCallback); - else - callback.onResponse(modules); + callback.onResponse(modules); }, error -> errorCallback.onError(new NetworkError(101115, error.networkResponse.statusCode, "Cannot get module list!"))); } diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesResources.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesResources.java index c09413a..5e0ca60 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesResources.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesResources.java @@ -47,7 +47,7 @@ public class ModulesResources extends PartModules> { errorCallback.onError(new NetworkError(101604, 500, "Currently running in offline mode!")); return; } - get(String.format("https://kvv.imp.fu-berlin.de/direct/content/site/%s.json", ID), mLogin.getLoginTokenKVV().getCookies(), response -> { + get(String.format("https://kvv.imp.fu-berlin.de/direct/content/site/%s.json?_validateSession=", ID), mLogin.getLoginTokenKVV().getCookies(), response -> { String body = response.getParsed(); if (body == null) { errorCallback.onError(new NetworkError(101601, 403, "No resources retrieved!")); @@ -116,11 +116,7 @@ public class ModulesResources extends PartModules> { } } - // Empty resources *may be* because token is invalid -> check - if (resources.size() == 0) - mLogin.testLoginToken(token -> callback.onResponse(root), errorCallback); - else - callback.onResponse(root); + callback.onResponse(root); }, error -> errorCallback.onError(new NetworkError(101603, error.networkResponse.statusCode, "Cannot get resources!"))); } From c7fe1dc3f76d72a1404b4e006e7063f22c166c2c Mon Sep 17 00:00:00 2001 From: Sebastian Seedorf Date: Fri, 8 Feb 2019 15:18:10 +0100 Subject: [PATCH 2/3] KVV not available implemented --- .../services/fulogin/UserLoginTask.java | 13 ++++++- .../fuplanner/services/kvv/sync/KVVLogin.java | 34 +++++++++++++------ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/de/sebse/fuplanner/services/fulogin/UserLoginTask.java b/app/src/main/java/de/sebse/fuplanner/services/fulogin/UserLoginTask.java index 25827ee..8f779c5 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/fulogin/UserLoginTask.java +++ b/app/src/main/java/de/sebse/fuplanner/services/fulogin/UserLoginTask.java @@ -17,6 +17,7 @@ import de.sebse.fuplanner.R; import de.sebse.fuplanner.services.kvv.sync.BBLogin; import de.sebse.fuplanner.services.kvv.sync.FULogin; import de.sebse.fuplanner.services.kvv.sync.KVVLogin; +import de.sebse.fuplanner.services.kvv.types.LoginTokenKVV; import de.sebse.fuplanner.tools.logging.Logger; import de.sebse.fuplanner.tools.network.NetworkError; import de.sebse.fuplanner.tools.network.NetworkErrorCallback; @@ -76,7 +77,17 @@ public class UserLoginTask extends AsyncTask { login.set(success1.toJsonString()); latch.countDown(); }, errorFunc); - }, errorFunc); + }, error -> { + if (error.getCode() == 100101) { + // KVV never used + LoginTokenKVV loginTokenKVV = new LoginTokenKVV(mUsername, ""); + loginTokenKVV.setNotAvailable(); + login.set(loginTokenKVV.toJsonString()); + latch.countDown(); + } else { + errorFunc.onError(error); + } + }); break; case AccountGeneral.AUTHTOKEN_TYPE_BLACKBOARD: mBBLogin.doLogin(mUsername, mPassword, success -> { diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/sync/KVVLogin.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/sync/KVVLogin.java index b91676e..ac9d7c5 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/sync/KVVLogin.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/sync/KVVLogin.java @@ -68,22 +68,36 @@ public class KVVLogin extends HTTPService { public void doLogin(String username, String password, NetworkCallback callback, NetworkErrorCallback error) { - step1(success1 -> { - String samlLocation = success1.get("Location"); - mFULogin.fulogin(samlLocation, username, password, samlResponse -> { - step5(samlResponse, success5 -> { - String shibsessionKey = success5.get("shibsessionKey"); - String shibsessionName = success5.get("shibsessionName"); - step6(shibsessionKey, shibsessionName, success6 -> { - String kvvJSESSIONID = success6.get("JSESSIONID"); - LoginTokenKVV token = new LoginTokenKVV(username, kvvJSESSIONID); - callback.onResponse(token); + step0(username, success -> { + step1(success1 -> { + String samlLocation = success1.get("Location"); + mFULogin.fulogin(samlLocation, username, password, samlResponse -> { + step5(samlResponse, success5 -> { + String shibsessionKey = success5.get("shibsessionKey"); + String shibsessionName = success5.get("shibsessionName"); + step6(shibsessionKey, shibsessionName, success6 -> { + String kvvJSESSIONID = success6.get("JSESSIONID"); + LoginTokenKVV token = new LoginTokenKVV(username, kvvJSESSIONID); + callback.onResponse(token); + }, error); }, error); }, error); }, error); }, error); } + private void step0(String username, final NetworkCallback callback, final NetworkErrorCallback errorCallback) { + get(String.format("https://kvv.imp.fu-berlin.de/direct/profile/%s", username), null, result -> { + callback.onResponse(true); + }, error -> { + if (error.networkResponse.statusCode == 500) { + errorCallback.onError(new NetworkError(100101, error.networkResponse.statusCode, "KVV not available!")); + } else { + callback.onResponse(true); + } + }); + } + /* 1= GET https://kvv.imp.fu-berlin.de/Shibboleth.sso/Login?entityID=https://identity.fu-berlin.de/idp-fub -> Location-Header: https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO?SAMLResponse=[SAMLResponse]&RelayState=[RelayState] From 14d5ca675903f497fb0984b6bef8c43cbcd9bb46 Mon Sep 17 00:00:00 2001 From: Sebastian Seedorf Date: Fri, 8 Feb 2019 16:29:10 +0100 Subject: [PATCH 3/3] Cache KVV courses --- .../fuplanner/services/kvv/ModulesList.java | 133 +++++++++++++----- .../services/kvv/types/CacheKVVCourse.java | 79 +++++++++++ 2 files changed, 174 insertions(+), 38 deletions(-) create mode 100644 app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheKVVCourse.java diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java index 767cbc1..ff97331 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java @@ -16,6 +16,7 @@ import java.util.regex.Matcher; import androidx.arch.core.util.Function; import de.sebse.fuplanner.services.kvv.types.CacheBBCourse; +import de.sebse.fuplanner.services.kvv.types.CacheKVVCourse; import de.sebse.fuplanner.services.kvv.types.Lecturer; import de.sebse.fuplanner.services.kvv.types.Modules; import de.sebse.fuplanner.services.kvv.types.Semester; @@ -35,6 +36,7 @@ public class ModulesList extends HTTPService { private ModulesListLecturer mLecturer; private CacheBBCourse mBBCache; private final NewAsyncQueue mQueue = new NewAsyncQueue(); + private CacheKVVCourse mKVVCache; ModulesList(Login login, KVVListener listener, Context context) { super(context); @@ -167,69 +169,109 @@ public class ModulesList extends HTTPService { } private void upgradeKVV(final NetworkCallback callback, final NetworkErrorCallback errorCallback) { + NetworkCallback successCallback = (modules -> { + try { + cacheKVVCourse().save(getContext()); + } catch (IOException e) { + e.printStackTrace(); + } + callback.onResponse(modules); + }); + if (!mLogin.isInOnlineMode() || mLogin.getLoginTokenKVV() == null) { errorCallback.onError(new NetworkError(101110, 500, "Currently running in offline mode!")); return; } + Modules modules = new Modules(mLogin.getLoginTokenKVV().getUsername()); if (!mLogin.getLoginTokenKVV().isAvailable()) { - callback.onResponse(new Modules(mLogin.getLoginTokenKVV().getUsername())); + callback.onResponse(modules); return; } - get("https://kvv.imp.fu-berlin.de/direct/site.json?_validateSession=", mLogin.getLoginTokenKVV().getCookies(), response -> { + get("https://kvv.imp.fu-berlin.de/direct/membership.json?_validateSession=", mLogin.getLoginTokenKVV().getCookies(), response -> { String body = response.getParsed(); if (body == null) { - errorCallback.onError(new NetworkError(101111, 403, "No module list retrieved!")); + errorCallback.onError(new NetworkError(101111, 403, "No membership list retrieved!")); return; } - Modules modules = new Modules(mLogin.getLoginTokenKVV().getUsername()); - JSONArray sites; + JSONArray memberships; try { JSONObject json = new JSONObject(body); - sites = json.getJSONArray("site_collection"); + memberships = json.getJSONArray("membership_collection"); } catch (JSONException e) { e.printStackTrace(); - errorCallback.onError(new NetworkError(101112, 403, "Cannot parse module list!")); + errorCallback.onError(new NetworkError(101112, 403, "Cannot parse membership list!")); return; } - for (int i = 0; i < sites.length(); i++) { + final int[] latch = {memberships.length()}; + for (int i = 0; i < memberships.length(); i++) { try { - JSONObject site = sites.getJSONObject(i); - String semester_string = site.getJSONObject("props").optString("term_eid", null); - Semester semester; - if (semester_string == null) - semester = null; - else - semester = new Semester(semester_string); - HashSet lvNumbers = new HashSet<>(); - String kvv_lvnumbers = site.getJSONObject("props").optString("kvv_lvnumbers", null); - if (kvv_lvnumbers != null) - for (MatchResult matchResult : Regex.allMatches("[0-9]+", kvv_lvnumbers)) { - lvNumbers.add(matchResult.group()); - } - String title = site.getString("entityTitle"); - LinkedHashSet lecturers = new LinkedHashSet<>(); - String kvv_lecturers = site.getJSONObject("props").optString("kvv_lecturers", null); - if (kvv_lecturers != null) for (String lecturer : kvv_lecturers.split("#")) { - if (lecturer.length() > 2) - lecturers.add(new Lecturer(lecturer)); + JSONObject membership = memberships.getJSONObject(i); + String locationReference = membership.getString("locationReference"); + String courseId = Regex.regex("/site/([0-9a-f-]+)", locationReference); + Modules.Module kvvCourse = cacheKVVCourse().getKVVCourse(courseId); + if (kvvCourse != null) { + kvvCourse = kvvCourse.clone(); + modules.addModule(kvvCourse); + if (--latch[0] == 0) successCallback.onResponse(modules); + continue; } - String type = site.getJSONObject("props").optString("kvv_coursetype", "Projekt"); - String description = site.optString("description", ""); - description = String.valueOf(PartModules.fromHtml(description)); - String id = site.getString("id"); - modules.addModule(semester, lvNumbers, title, lecturers, type, description, id, Modules.TYPE_KVV); + get(String.format("https://kvv.imp.fu-berlin.de/direct/site/%s.json?_validateSession=", courseId), mLogin.getLoginTokenKVV().getCookies(), response1 -> { + String body1 = response1.getParsed(); + if (body1 == null) { + errorCallback.onError(new NetworkError(101113, 403, "No site retrieved!")); + return; + } + try { + JSONObject site = new JSONObject(body1); + + String semester_string = site.getJSONObject("props").optString("term_eid", null); + Semester semester; + if (semester_string == null) + semester = null; + else + semester = new Semester(semester_string); + HashSet lvNumbers = new HashSet<>(); + String kvv_lvnumbers = site.getJSONObject("props").optString("kvv_lvnumbers", null); + if (kvv_lvnumbers != null) + for (MatchResult matchResult : Regex.allMatches("[0-9]+", kvv_lvnumbers)) { + lvNumbers.add(matchResult.group()); + } + String title = site.getString("entityTitle"); + LinkedHashSet lecturers = new LinkedHashSet<>(); + String kvv_lecturers = site.getJSONObject("props").optString("kvv_lecturers", null); + if (kvv_lecturers != null) for (String lecturer : kvv_lecturers.split("#")) { + if (lecturer.length() > 2) + lecturers.add(new Lecturer(lecturer)); + } + String type = site.getJSONObject("props").optString("kvv_coursetype", "Projekt"); + String description = site.optString("description", ""); + description = String.valueOf(PartModules.fromHtml(description)); + String id = site.getString("id"); + Modules.Module module = modules.addModule(semester, lvNumbers, title, lecturers, type, description, id, Modules.TYPE_KVV); + cacheKVVCourse().setKVVCourse(courseId, module.clone()); + if (--latch[0] == 0) successCallback.onResponse(modules); + } catch (JSONException e) { + log.e(new NetworkError(101114, 403, "Cannot parse site!")); + log.e("JSON:", body1); + e.printStackTrace(); + } catch (NoSuchFieldException e) { + log.e(new NetworkError(101115, 403, "Cannot parse site!")); + e.printStackTrace(); + } + }, error -> errorCallback.onError(new NetworkError(101116, error.networkResponse.statusCode, "Cannot get membership list!"))); } catch (JSONException e) { - log.e(new NetworkError(101113, 403, "Cannot parse module list!")); - log.e("ID:", i, "JSON:", sites); + log.e("ID:", i, "JSON:", memberships); e.printStackTrace(); + errorCallback.onError(new NetworkError(101117, 403, "Cannot parse membership list!")); + return; } catch (NoSuchFieldException e) { - log.e(new NetworkError(101114, 403, "Cannot parse module list!")); - log.e("ID:", i, "JSON:", sites); + log.e("ID:", i, "JSON:", memberships); e.printStackTrace(); + errorCallback.onError(new NetworkError(101118, 403, "Cannot parse membership list!")); + return; } } - callback.onResponse(modules); - }, error -> errorCallback.onError(new NetworkError(101115, error.networkResponse.statusCode, "Cannot get module list!"))); + }, error -> errorCallback.onError(new NetworkError(101119, error.networkResponse.statusCode, "Cannot get membership list!"))); } private void upgradeBB(final Modules modulesKVV, final NetworkCallback callback, final NetworkErrorCallback errorCallback) { @@ -363,4 +405,19 @@ public class ModulesList extends HTTPService { mBBCache = new CacheBBCourse(); return mBBCache; } + + private CacheKVVCourse cacheKVVCourse() { + if (mKVVCache == null) { + try { + mKVVCache = CacheKVVCourse.load(getContext()); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + if (mKVVCache == null) + mKVVCache = new CacheKVVCourse(); + return mKVVCache; + } } diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheKVVCourse.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheKVVCourse.java new file mode 100644 index 0000000..10dd993 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheKVVCourse.java @@ -0,0 +1,79 @@ +package de.sebse.fuplanner.services.kvv.types; + +import android.content.Context; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.HashSet; + +public class CacheKVVCourse implements Serializable { + private transient static final long RESAVE_TIMER = 1000L * 60 * 60 * 24 * 30; + private static final String FILE_NAME = "KVVCourseStorageSaving"; + private static final String FILE_NAME_TIMESTAMP = "KVVCourseStorageSavingTimestamp"; + private transient long mLastTimestamp = 0; + + private HashMap mKVVCourseList = new HashMap<>(); + private HashMap mKVVCourseListRefresh = new HashMap<>(); + + public static CacheKVVCourse load(Context context) throws IOException, ClassNotFoundException { + FileInputStream fis = context.openFileInput(FILE_NAME); + ObjectInputStream is = new ObjectInputStream(fis); + Object readObject = is.readObject(); + if (!(readObject instanceof CacheKVVCourse)) + return null; + CacheKVVCourse storage = (CacheKVVCourse) readObject; + is.close(); + fis.close(); + + fis = context.openFileInput(FILE_NAME_TIMESTAMP); + is = new ObjectInputStream(fis); + storage.mLastTimestamp = is.readLong(); + is.close(); + fis.close(); + + return storage; + } + + public boolean isNewerVersionInStorage(Context context) throws IOException { + FileInputStream fis = context.openFileInput(FILE_NAME_TIMESTAMP); + ObjectInputStream is = new ObjectInputStream(fis); + boolean result = this.mLastTimestamp < is.readLong(); + is.close(); + fis.close(); + return result; + } + + public void save(Context context) throws IOException { + FileOutputStream fos = context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE); + ObjectOutputStream os = new ObjectOutputStream(fos); + os.writeObject(this); + os.close(); + fos.close(); + + fos = context.openFileOutput(FILE_NAME_TIMESTAMP, Context.MODE_PRIVATE); + os = new ObjectOutputStream(fos); + this.mLastTimestamp = System.currentTimeMillis(); + os.writeLong(this.mLastTimestamp); + os.close(); + fos.close(); + } + + public void setKVVCourse(String courseID, Modules.Module lecturer) { + mKVVCourseList.put(courseID, lecturer); + mKVVCourseListRefresh.put(courseID, System.currentTimeMillis()); + } + + public Modules.Module getKVVCourse(String courseID) { + if (!mKVVCourseListRefresh.containsKey(courseID)) + return null; + //noinspection ConstantConditions + if (mKVVCourseListRefresh.get(courseID) + RESAVE_TIMER < System.currentTimeMillis()) + return null; + return mKVVCourseList.get(courseID); + } +}