[APIv2] Add API Service. Update other APIv2 components. Update Profile DAO

This commit is contained in:
Kuba Szczodrzyński 2019-09-29 17:19:38 +02:00
parent 5edd4d5922
commit 0bf2026a64
51 changed files with 843 additions and 265 deletions

View File

@ -218,6 +218,8 @@
android:name=".sync.SyncService" android:name=".sync.SyncService"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/sync_service" /> android:label="@string/sync_service" />
<service android:name=".api.v2.ApiService" />
</application> </application>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

View File

@ -19,7 +19,6 @@ import android.provider.Settings;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.widget.Toast;
import com.chuckerteam.chucker.api.ChuckerCollector; import com.chuckerteam.chucker.api.ChuckerCollector;
import com.chuckerteam.chucker.api.ChuckerInterceptor; import com.chuckerteam.chucker.api.ChuckerInterceptor;
@ -81,7 +80,6 @@ import pl.szczodrzynski.edziennik.datamodels.ProfileFull;
import pl.szczodrzynski.edziennik.models.AppConfig; import pl.szczodrzynski.edziennik.models.AppConfig;
import pl.szczodrzynski.edziennik.network.NetworkUtils; import pl.szczodrzynski.edziennik.network.NetworkUtils;
import pl.szczodrzynski.edziennik.network.TLSSocketFactory; import pl.szczodrzynski.edziennik.network.TLSSocketFactory;
import pl.szczodrzynski.edziennik.receivers.BootReceiver;
import pl.szczodrzynski.edziennik.receivers.JobsCreator; import pl.szczodrzynski.edziennik.receivers.JobsCreator;
import pl.szczodrzynski.edziennik.sync.SyncJob; import pl.szczodrzynski.edziennik.sync.SyncJob;
import pl.szczodrzynski.edziennik.utils.PermissionChecker; import pl.szczodrzynski.edziennik.utils.PermissionChecker;
@ -617,7 +615,7 @@ public class App extends androidx.multidex.MultiDexApplication {
} }
public ProfileFull profileGetOrNull(int id) { public ProfileFull profileGetOrNull(int id) {
return db.profileDao().getByIdNow(id); return db.profileDao().getFullByIdNow(id);
} }
public void profileLoadById(int id) { public void profileLoadById(int id) {
@ -632,7 +630,7 @@ public class App extends androidx.multidex.MultiDexApplication {
return; return;
}*/ }*/
if (profile == null || profile.getId() != id) { if (profile == null || profile.getId() != id) {
profile = db.profileDao().getByIdNow(id); profile = db.profileDao().getFullByIdNow(id);
/*if (profile == null) { /*if (profile == null) {
profileLoadById(id); profileLoadById(id);
return; return;
@ -659,7 +657,7 @@ public class App extends androidx.multidex.MultiDexApplication {
/*public void profileRemove(int id) /*public void profileRemove(int id)
{ {
Profile profile = db.profileDao().getByIdNow(id); Profile profile = db.profileDao().getFullByIdNow(id);
if (profile.id == profile.loginStoreId) { if (profile.id == profile.loginStoreId) {
// this profile is the owner of the login store // this profile is the owner of the login store

View File

@ -10,10 +10,13 @@ import androidx.core.app.ActivityCompat
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
import im.wangchao.mhttp.Response
import pl.szczodrzynski.edziennik.datamodels.Profile import pl.szczodrzynski.edziennik.datamodels.Profile
import pl.szczodrzynski.edziennik.datamodels.Teacher import pl.szczodrzynski.edziennik.datamodels.Teacher
import pl.szczodrzynski.navlib.crc16 import pl.szczodrzynski.navlib.crc16
import pl.szczodrzynski.navlib.getColorFromRes import pl.szczodrzynski.navlib.getColorFromRes
import java.text.SimpleDateFormat
import java.util.*
fun List<Teacher>.byId(id: Long) = firstOrNull { it.id == id } fun List<Teacher>.byId(id: Long) = firstOrNull { it.id == id }
@ -117,4 +120,11 @@ fun Activity.isStoragePermissionGranted(): Boolean {
} else { } else {
true true
} }
}
fun Response?.getUnixDate(): Long {
val rfcDate = this?.headers()?.get("date") ?: return currentTimeUnix()
val pattern = "EEE, dd MMM yyyy HH:mm:ss Z"
val format = SimpleDateFormat(pattern, Locale.ENGLISH)
return format.parse(rfcDate).time / 1000
} }

View File

@ -41,7 +41,7 @@ import com.mikepenz.materialdrawer.model.interfaces.IProfile
import pl.droidsonroids.gif.GifDrawable import pl.droidsonroids.gif.GifDrawable
import pl.szczodrzynski.edziennik.App.APP_URL import pl.szczodrzynski.edziennik.App.APP_URL
import pl.szczodrzynski.edziennik.api.AppError import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.* import pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface.*
import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback
import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding
import pl.szczodrzynski.edziennik.datamodels.LoginStore import pl.szczodrzynski.edziennik.datamodels.LoginStore

View File

@ -244,7 +244,7 @@ public class WidgetTimetable extends AppWidgetProvider {
filterOutArchived(profileList); filterOutArchived(profileList);
} }
else { else {
Profile profile = app.db.profileDao().getByIdNow(widgetConfig.profileId); Profile profile = app.db.profileDao().getFullByIdNow(widgetConfig.profileId);
if (profile != null) { if (profile != null) {
profileList.add(profile); profileList.add(profile);
} }

View File

@ -43,7 +43,7 @@ import pl.szczodrzynski.edziennik.BuildConfig;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.MainActivity; import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.WidgetTimetable; import pl.szczodrzynski.edziennik.WidgetTimetable;
import pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface; import pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface;
import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback; import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback;
import pl.szczodrzynski.edziennik.datamodels.AnnouncementFull; import pl.szczodrzynski.edziennik.datamodels.AnnouncementFull;
import pl.szczodrzynski.edziennik.datamodels.Attendance; import pl.szczodrzynski.edziennik.datamodels.Attendance;
@ -81,16 +81,16 @@ import static pl.szczodrzynski.edziennik.api.AppError.CODE_PROFILE_ARCHIVED;
import static pl.szczodrzynski.edziennik.api.AppError.CODE_PROFILE_NOT_FOUND; import static pl.szczodrzynski.edziennik.api.AppError.CODE_PROFILE_NOT_FOUND;
import static pl.szczodrzynski.edziennik.api.AppError.stringErrorCode; import static pl.szczodrzynski.edziennik.api.AppError.stringErrorCode;
import static pl.szczodrzynski.edziennik.api.AppError.stringErrorType; import static pl.szczodrzynski.edziennik.api.AppError.stringErrorType;
import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_AGENDA; import static pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface.FEATURE_AGENDA;
import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_ALL; import static pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface.FEATURE_ALL;
import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_ANNOUNCEMENTS; import static pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface.FEATURE_ANNOUNCEMENTS;
import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_ATTENDANCES; import static pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface.FEATURE_ATTENDANCES;
import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_GRADES; import static pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface.FEATURE_GRADES;
import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_HOMEWORKS; import static pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface.FEATURE_HOMEWORKS;
import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_MESSAGES_INBOX; import static pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface.FEATURE_MESSAGES_INBOX;
import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_MESSAGES_OUTBOX; import static pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface.FEATURE_MESSAGES_OUTBOX;
import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_NOTICES; import static pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface.FEATURE_NOTICES;
import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_TIMETABLE; import static pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface.FEATURE_TIMETABLE;
import static pl.szczodrzynski.edziennik.datamodels.Event.TYPE_HOMEWORK; import static pl.szczodrzynski.edziennik.datamodels.Event.TYPE_HOMEWORK;
import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_FINAL; import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_FINAL;
import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_PROPOSED; import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_PROPOSED;
@ -115,7 +115,7 @@ public class Edziennik {
private static boolean registerEmpty; private static boolean registerEmpty;
public static int oldLuckyNumber; public static int oldLuckyNumber;
public static EdziennikInterface getApi(App app, int loginType) { public static OldEdziennikInterface getApi(App app, int loginType) {
switch (loginType) { switch (loginType) {
default: default:
case LOGIN_TYPE_MOBIDZIENNIK: case LOGIN_TYPE_MOBIDZIENNIK:
@ -625,7 +625,7 @@ public class Edziennik {
* Used in services, login form and {@code guiSync} * Used in services, login form and {@code guiSync}
* <p> * <p>
* May be ran on worker thread. * May be ran on worker thread.
* {@link EdziennikInterface}.sync is ran always on worker thread. * {@link OldEdziennikInterface}.sync is ran always on worker thread.
* Every callback is ran on the UI thread. * Every callback is ran on the UI thread.
* *
* @param app * @param app
@ -676,7 +676,7 @@ public class Edziennik {
} }
}; };
AsyncTask.execute(() -> { AsyncTask.execute(() -> {
ProfileFull profile = app.db.profileDao().getByIdNow(profileId); ProfileFull profile = app.db.profileDao().getFullByIdNow(profileId);
if (profile != null) { if (profile != null) {
if (profile.getArchived()) { if (profile.getArchived()) {
@ -1130,7 +1130,7 @@ public class Edziennik {
.show(); .show();
} }
public void removeProfile(int profileId) { public void removeProfile(int profileId) {
Profile profileObject = app.db.profileDao().getByIdNow(profileId); Profile profileObject = app.db.profileDao().getFullByIdNow(profileId);
if (profileObject == null) if (profileObject == null)
return; return;
app.db.announcementDao().clear(profileId); app.db.announcementDao().clear(profileId);

View File

@ -35,7 +35,7 @@ import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.BuildConfig; import pl.szczodrzynski.edziennik.BuildConfig;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.api.interfaces.AttachmentGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.AttachmentGetCallback;
import pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface; import pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface;
import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback; import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback;
import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback;
import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback;
@ -91,7 +91,7 @@ import static pl.szczodrzynski.edziennik.utils.Utils.crc32;
import static pl.szczodrzynski.edziennik.utils.Utils.d; import static pl.szczodrzynski.edziennik.utils.Utils.d;
import static pl.szczodrzynski.edziennik.utils.Utils.getWordGradeValue; import static pl.szczodrzynski.edziennik.utils.Utils.getWordGradeValue;
public class Iuczniowie implements EdziennikInterface { public class Iuczniowie implements OldEdziennikInterface {
public Iuczniowie(App app) { public Iuczniowie(App app) {
this.app = app; this.app = app;
} }

View File

@ -46,7 +46,7 @@ import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.BuildConfig; import pl.szczodrzynski.edziennik.BuildConfig;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.api.interfaces.AttachmentGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.AttachmentGetCallback;
import pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface; import pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface;
import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback; import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback;
import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback;
import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback;
@ -131,7 +131,7 @@ import static pl.szczodrzynski.edziennik.utils.Utils.d;
import static pl.szczodrzynski.edziennik.utils.Utils.getGradeValue; import static pl.szczodrzynski.edziennik.utils.Utils.getGradeValue;
import static pl.szczodrzynski.edziennik.utils.Utils.strToInt; import static pl.szczodrzynski.edziennik.utils.Utils.strToInt;
public class Librus implements EdziennikInterface { public class Librus implements OldEdziennikInterface {
public Librus(App app) { public Librus(App app) {
this.app = app; this.app = app;
} }
@ -2537,7 +2537,7 @@ public class Librus implements EdziennikInterface {
&& (el = obj.get("Id")) != null) { && (el = obj.get("Id")) != null) {
type = el.getAsInt(); type = el.getAsInt();
} }
/*EventType typeObject = app.db.eventTypeDao().getByIdNow(profileId, type); /*EventType typeObject = app.db.eventTypeDao().getFullByIdNow(profileId, type);
if (typeObject == null) { if (typeObject == null) {
getCustomTypes = true; getCustomTypes = true;
}*/ }*/

View File

@ -41,7 +41,7 @@ import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.BuildConfig; import pl.szczodrzynski.edziennik.BuildConfig;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.api.interfaces.AttachmentGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.AttachmentGetCallback;
import pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface; import pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface;
import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback; import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback;
import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback;
import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback;
@ -99,7 +99,7 @@ import static pl.szczodrzynski.edziennik.utils.Utils.d;
import static pl.szczodrzynski.edziennik.utils.Utils.monthFromName; import static pl.szczodrzynski.edziennik.utils.Utils.monthFromName;
import static pl.szczodrzynski.edziennik.utils.Utils.strToInt; import static pl.szczodrzynski.edziennik.utils.Utils.strToInt;
public class Mobidziennik implements EdziennikInterface { public class Mobidziennik implements OldEdziennikInterface {
public Mobidziennik(App app) { public Mobidziennik(App app) {
this.app = app; this.app = app;
} }

View File

@ -36,7 +36,7 @@ import okio.Buffer;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.api.interfaces.AttachmentGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.AttachmentGetCallback;
import pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface; import pl.szczodrzynski.edziennik.api.interfaces.OldEdziennikInterface;
import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback; import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback;
import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback;
import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback; import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback;
@ -93,7 +93,7 @@ import static pl.szczodrzynski.edziennik.utils.Utils.getGradeValue;
import static pl.szczodrzynski.edziennik.utils.Utils.getVulcanGradeColor; import static pl.szczodrzynski.edziennik.utils.Utils.getVulcanGradeColor;
import static pl.szczodrzynski.edziennik.utils.Utils.intToStr; import static pl.szczodrzynski.edziennik.utils.Utils.intToStr;
public class Vulcan implements EdziennikInterface { public class Vulcan implements OldEdziennikInterface {
public Vulcan(App app) { public Vulcan(App app) {
this.app = app; this.app = app;
} }

View File

@ -14,7 +14,7 @@ import pl.szczodrzynski.edziennik.datamodels.ProfileFull;
import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo; import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo;
import pl.szczodrzynski.edziennik.models.Endpoint; import pl.szczodrzynski.edziennik.models.Endpoint;
public interface EdziennikInterface { public interface OldEdziennikInterface {
/** /**
* Sync all Edziennik data. * Sync all Edziennik data.

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.events.requests.MessageGetRequest
import pl.szczodrzynski.edziennik.api.v2.events.SyncProgressEvent
import pl.szczodrzynski.edziennik.api.v2.events.requests.SyncProfileRequest
import pl.szczodrzynski.edziennik.api.v2.events.requests.SyncRequest
import pl.szczodrzynski.edziennik.api.v2.events.requests.SyncViewRequest
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.librus.Librus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.models.ApiTask
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
import kotlin.math.min
class ApiService : Service() {
companion object {
const val TAG = "ApiService"
const val NOTIFICATION_API_CHANNEL_ID = "pl.szczodrzynski.edziennik.GET_DATA"
}
private val app by lazy { applicationContext as App }
private val taskQueue = mutableListOf<ApiTask>()
private val errorList = mutableListOf<ApiError>()
private var taskRunning = false
private var taskRunningId = -1
private var taskMaximumId = 0
private var taskProfileId = -1
private var taskProfileName: String? = null
private var taskProgress = 0
private var taskProgressRes: Int? = null
private val taskCallback = object : EdziennikCallback {
override fun onCompleted() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onError(apiError: ApiError) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onProgress(step: Int) {
taskProgress += step
taskProgress = min(100, taskProgress)
EventBus.getDefault().post(SyncProgressEvent(taskProfileId, taskProfileName, taskProgress, taskProgressRes))
}
override fun onStartProgress(stringRes: Int) {
}
}
private fun sync() {
if (taskRunning)
return
if (taskQueue.size <= 0)
return // TODO stopSelf() or sth
val task = taskQueue.removeAt(0)
taskRunning = true
taskRunningId = task.taskId
// get the requested profile and login store
val profile: Profile? = app.db.profileDao().getByIdNow(task.profileId)
if (profile == null || !profile.syncEnabled) {
return
}
val loginStore: LoginStore? = app.db.loginStoreDao().getByIdNow(profile.loginStoreId)
if (loginStore == null) {
return
}
// save the profile ID and name as the current task's
taskProfileId = profile.id
taskProfileName = profile.name
taskProgress = 0
taskProgressRes = null
val edziennikInterface = when (loginStore.type) {
LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback)
else -> null
}
if (edziennikInterface == null) {
return
}
when (task) {
is SyncProfileRequest -> edziennikInterface.sync(task.featureIds ?: Features.getAllIds())
is SyncViewRequest -> edziennikInterface.sync(Features.getIdsByView(task.targetId))
is MessageGetRequest -> edziennikInterface.getMessage(task.messageId)
}
}
@Subscribe(threadMode = ThreadMode.ASYNC)
fun onSyncRequest(syncRequest: SyncRequest) {
app.db.profileDao().idsForSyncNow.forEach { id ->
taskQueue += SyncProfileRequest(id, null)
}
sync()
}
@Subscribe(threadMode = ThreadMode.ASYNC)
fun onSyncProfileRequest(syncProfileRequest: SyncProfileRequest) {
Log.d(TAG, syncProfileRequest.toString())
taskQueue += syncProfileRequest
sync()
}
@Subscribe(threadMode = ThreadMode.ASYNC)
fun onMessageGetRequest(messageGetRequest: MessageGetRequest) {
Log.d(TAG, messageGetRequest.toString())
taskQueue += messageGetRequest
sync()
}
private val notification by lazy {
NotificationCompat.Builder(this, NOTIFICATION_API_CHANNEL_ID)
.setContentTitle("API")
.setContentText("API is running")
.setSmallIcon(R.drawable.ic_notification)
.build()
}
override fun onCreate() {
EventBus.getDefault().register(this)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(1, notification)
return START_NOT_STICKY
}
override fun onDestroy() {
EventBus.getDefault().unregister(this)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}

View File

@ -4,18 +4,6 @@
package pl.szczodrzynski.edziennik.api.v2 package pl.szczodrzynski.edziennik.api.v2
internal const val FEATURE_ANY = -1
const val FEATURE_ALL = 0
const val FEATURE_TIMETABLE = 1
const val FEATURE_AGENDA = 2
const val FEATURE_GRADES = 3
const val FEATURE_HOMEWORKS = 4
const val FEATURE_NOTICES = 5
const val FEATURE_ATTENDANCES = 6
const val FEATURE_MESSAGES_INBOX = 7
const val FEATURE_MESSAGES_OUTBOX = 8
const val FEATURE_ANNOUNCEMENTS = 9
const val LIBRUS_USER_AGENT = "Dalvik/2.1.0 Android LibrusMobileApp" const val LIBRUS_USER_AGENT = "Dalvik/2.1.0 Android LibrusMobileApp"
const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0" const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"
const val LIBRUS_CLIENT_ID = "wmSyUMo8llDAs4y9tJVYY92oyZ6h4lAt7KCuy0Gv" const val LIBRUS_CLIENT_ID = "wmSyUMo8llDAs4y9tJVYY92oyZ6h4lAt7KCuy0Gv"
@ -42,7 +30,7 @@ const val LIBRUS_JST_DEMO_CODE = "68656A21"
const val LIBRUS_JST_DEMO_PIN = "1290" const val LIBRUS_JST_DEMO_PIN = "1290"
/** https://synergia.librus.pl/loguj/token/TOKEN/przenies */ /** https://synergia.librus.pl/loguj/token/TOKEN/przenies */
const val LIBRUS_SYNERGIA_TOKEN_LOGIN_URL = "https://synergia.librus.pl/loguj/token/TOKEN/przenies/" const val LIBRUS_SYNERGIA_TOKEN_LOGIN_URL = "https://synergia.librus.pl/loguj/token/TOKEN/przenies"
const val LIBRUS_MESSAGES_URL = "https://wiadomosci.librus.pl/module/" const val LIBRUS_MESSAGES_URL = "https://wiadomosci.librus.pl/module/"
const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action=" const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action="

View File

@ -4,20 +4,130 @@
package pl.szczodrzynski.edziennik.api.v2 package pl.szczodrzynski.edziennik.api.v2
import android.util.Log
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApiGrades
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApiMe
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusSynergiaGrades
import pl.szczodrzynski.edziennik.api.v2.models.Endpoint import pl.szczodrzynski.edziennik.api.v2.models.Endpoint
const val ENDPOINT_LIBRUS_API_ME = 0 const val ENDPOINT_LIBRUS_API_ME = 101
const val ENDPOINT_LIBRUS_API_GRADES = 0 const val ENDPOINT_LIBRUS_API_SCHOOLS = 102
const val ENDPOINT_LIBRUS_SYNERGIA_GRADES = 0 const val ENDPOINT_LIBRUS_API_CLASSES = 103
const val ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES = 104
const val ENDPOINT_LIBRUS_API_UNITS = 105
const val ENDPOINT_LIBRUS_API_USERS = 106
const val ENDPOINT_LIBRUS_API_SUBJECTS = 107
const val ENDPOINT_LIBRUS_API_CLASSROOMS = 108
const val ENDPOINT_LIBRUS_API_TIMETABLES = 109
const val ENDPOINT_LIBRUS_API_SUBSTITUTIONS = 110
const val ENDPOINT_LIBRUS_API_NORMAL_GC = 111
const val ENDPOINT_LIBRUS_API_POINT_GC = 112
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GC = 113
const val ENDPOINT_LIBRUS_API_TEXT_GC = 114
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC = 115
const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GC = 116
const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 117
const val ENDPOINT_LIBRUS_API_POINT_GRADES = 118
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 119
const val ENDPOINT_LIBRUS_API_TEXT_GRADES = 120
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES = 121
const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES = 122
const val ENDPOINT_LIBRUS_API_EVENTS = 123
const val ENDPOINT_LIBRUS_API_EVENT_TYPES = 124
const val ENDPOINT_LIBRUS_API_HOMEWORK = 125
const val ENDPOINT_LIBRUS_API_LUCKY_NUMBER = 126
const val ENDPOINT_LIBRUS_API_NOTICES = 127
const val ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES = 128
const val ENDPOINT_LIBRUS_API_ATTENDANCE = 129
const val ENDPOINT_LIBRUS_API_ANNOUNCEMENTS = 130
const val ENDPOINT_LIBRUS_API_PT_MEETINGS = 131
const val ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS = 132
const val ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS = 133
const val ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS = 134
const val ENDPOINT_LIBRUS_SYNERGIA_INFO = 201
const val ENDPOINT_LIBRUS_SYNERGIA_GRADES = 202
const val ENDPOINT_LIBRUS_MESSAGES_RECEIVED = 301
const val ENDPOINT_LIBRUS_MESSAGES_SENT = 302
const val ENDPOINT_LIBRUS_MESSAGES_TRASH = 303
const val ENDPOINT_LIBRUS_MESSAGES_RECEIVERS = 304
const val ENDPOINT_LIBRUS_MESSAGES_GET = 304
val endpoints = listOf( val endpoints = listOf(
Endpoint(LOGIN_TYPE_LIBRUS, ENDPOINT_LIBRUS_API_ME, null, LibrusApiMe::class.java) { _, _ -> LOGIN_METHOD_LIBRUS_API},
Endpoint(LOGIN_TYPE_LIBRUS, 1, listOf(), LibrusSynergiaGrades::class.java) { _, _ -> LOGIN_METHOD_LIBRUS_SYNERGIA }, // LIBRUS: API
Endpoint(LOGIN_TYPE_LIBRUS, 1, listOf(), LibrusApiGrades::class.java) { _, _ -> LOGIN_METHOD_LIBRUS_API } Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_TIMETABLE, listOf(
ENDPOINT_LIBRUS_API_TIMETABLES,
ENDPOINT_LIBRUS_API_SUBSTITUTIONS
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_AGENDA, listOf(
ENDPOINT_LIBRUS_API_EVENTS,
ENDPOINT_LIBRUS_API_EVENT_TYPES,
ENDPOINT_LIBRUS_API_PT_MEETINGS,
ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS,
ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS,
ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf(
ENDPOINT_LIBRUS_API_NORMAL_GC,
ENDPOINT_LIBRUS_API_POINT_GC,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_GC,
ENDPOINT_LIBRUS_API_TEXT_GC,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC,
ENDPOINT_LIBRUS_API_BEHAVIOUR_GC,
ENDPOINT_LIBRUS_API_NORMAL_GRADES,
ENDPOINT_LIBRUS_API_POINT_GRADES,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES,
ENDPOINT_LIBRUS_API_TEXT_GRADES,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES,
ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf(
ENDPOINT_LIBRUS_API_HOMEWORK
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_NOTICES, listOf(
ENDPOINT_LIBRUS_API_NOTICES
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_ATTENDANCES, listOf(
ENDPOINT_LIBRUS_API_ATTENDANCE,
ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_ANNOUNCEMENTS, listOf(
ENDPOINT_LIBRUS_API_ANNOUNCEMENTS
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_STUDENT_INFO, listOf(
ENDPOINT_LIBRUS_API_ME
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_SCHOOL_INFO, listOf(
ENDPOINT_LIBRUS_API_SCHOOLS,
ENDPOINT_LIBRUS_API_UNITS
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_CLASS_INFO, listOf(
ENDPOINT_LIBRUS_API_CLASSES
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_TEAM_INFO, listOf(
ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_LUCKY_NUMBER, listOf(
ENDPOINT_LIBRUS_API_LUCKY_NUMBER
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_TEACHERS, listOf(
ENDPOINT_LIBRUS_API_USERS
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_SUBJECTS, listOf(
ENDPOINT_LIBRUS_API_SUBJECTS
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_CLASSROOMS, listOf(
ENDPOINT_LIBRUS_API_CLASSROOMS
), LOGIN_METHOD_LIBRUS_API),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_STUDENT_INFO, listOf(
ENDPOINT_LIBRUS_SYNERGIA_INFO
), LOGIN_METHOD_LIBRUS_SYNERGIA),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_STUDENT_NUMBER, listOf(
ENDPOINT_LIBRUS_SYNERGIA_INFO
), LOGIN_METHOD_LIBRUS_SYNERGIA),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf(
ENDPOINT_LIBRUS_SYNERGIA_GRADES
), LOGIN_METHOD_LIBRUS_SYNERGIA),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_MESSAGES_INBOX, listOf(ENDPOINT_LIBRUS_MESSAGES_RECEIVED), LOGIN_METHOD_LIBRUS_MESSAGES),
Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_MESSAGES_OUTBOX, listOf(ENDPOINT_LIBRUS_MESSAGES_SENT), LOGIN_METHOD_LIBRUS_MESSAGES)
) )
/* /*

View File

@ -59,11 +59,6 @@ const val ERROR_LOGIN_LIBRUS_API_OTHER = 131
const val ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING = 132 const val ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING = 132
const val ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED = 133 const val ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED = 133
const val ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR = 134 const val ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR = 134
const val ERROR_LOGIN_LIBRUS_PORTAL_TOKEN_ERROR = 135
const val ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_DISCONNECTED = 136
const val ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_410 = 137
const val ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_NOT_FOUND = 138
const val ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_OTHER = 139
const val ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_TOKEN_MISSING = 139 const val ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_TOKEN_MISSING = 139
const val ERROR_LIBRUS_API_TOKEN_EXPIRED = 140 const val ERROR_LIBRUS_API_TOKEN_EXPIRED = 140
const val ERROR_LIBRUS_API_INSUFFICIENT_SCOPES = 141 const val ERROR_LIBRUS_API_INSUFFICIENT_SCOPES = 141
@ -83,8 +78,22 @@ const val ERROR_LOGIN_LIBRUS_SYNERGIA_NO_SESSION_ID = 154
const val ERROR_LIBRUS_MESSAGES_ACCESS_DENIED = 155 const val ERROR_LIBRUS_MESSAGES_ACCESS_DENIED = 155
const val ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED = 156 const val ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED = 156
const val ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID = 157 const val ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID = 157
const val ERROR_LIBRUS_PORTAL_TOKEN_EXPIRED = 158
const val ERROR_LIBRUS_PORTAL_API_DISABLED = 159
const val ERROR_LIBRUS_PORTAL_SYNERGIA_DISCONNECTED = 160
const val ERROR_LIBRUS_PORTAL_OTHER = 161
const val ERROR_LIBRUS_PORTAL_SYNERGIA_NOT_FOUND = 162
const val ERROR_LOGIN_LIBRUS_PORTAL_OTHER = 163
const val ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED = 164
const val ERROR_LOGIN_LIBRUS_PORTAL_CODE_REVOKED = 165
const val ERROR_LOGIN_LIBRUS_PORTAL_NO_CLIENT_ID = 166
const val ERROR_LOGIN_LIBRUS_PORTAL_NO_CODE = 167
const val ERROR_LOGIN_LIBRUS_PORTAL_NO_REFRESH = 168
const val ERROR_LOGIN_LIBRUS_PORTAL_NO_REDIRECT = 169
const val ERROR_LOGIN_LIBRUS_PORTAL_UNSUPPORTED_GRANT = 170
const val ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID = 171
const val EXCEPTION_LOGIN_LIBRUS_API_TOKEN = 901 const val EXCEPTION_LOGIN_LIBRUS_API_TOKEN = 901
const val EXCEPTION_LOGIN_LIBRUS_PORTAL_TOKEN = 902 const val EXCEPTION_LOGIN_LIBRUS_PORTAL_TOKEN = 902
const val EXCEPTION_LOGIN_LIBRUS_PORTAL_SYNERGIA_TOKEN = 903 const val EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN = 903
const val EXCEPTION_LIBRUS_API_REQUEST = 904 const val EXCEPTION_LIBRUS_API_REQUEST = 904

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-29.
*/
package pl.szczodrzynski.edziennik.api.v2
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORKS
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_NOTICES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.datamodels.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.datamodels.Message.TYPE_SENT
import pl.szczodrzynski.edziennik.messages.MessagesFragment
import pl.szczodrzynski.edziennik.messages.MessagesListFragment
const val FEATURE_ALL = 0
const val FEATURE_TIMETABLE = 1
const val FEATURE_AGENDA = 2
const val FEATURE_GRADES = 3
const val FEATURE_HOMEWORK = 4
const val FEATURE_NOTICES = 5
const val FEATURE_ATTENDANCES = 6
const val FEATURE_MESSAGES_INBOX = 7
const val FEATURE_MESSAGES_OUTBOX = 8
const val FEATURE_ANNOUNCEMENTS = 9
const val FEATURE_STUDENT_INFO = 101
const val FEATURE_STUDENT_NUMBER = 109
const val FEATURE_SCHOOL_INFO = 102
const val FEATURE_CLASS_INFO = 103
const val FEATURE_TEAM_INFO = 104
const val FEATURE_LUCKY_NUMBER = 105
const val FEATURE_TEACHERS = 106
const val FEATURE_SUBJECTS = 107
const val FEATURE_CLASSROOMS = 108
const val FEATURE_MESSAGE_GET = 201
object Features {
private fun getAllNecessary(): List<Int> = listOf(
FEATURE_STUDENT_INFO,
FEATURE_STUDENT_NUMBER,
FEATURE_SCHOOL_INFO,
FEATURE_CLASS_INFO,
FEATURE_TEAM_INFO,
FEATURE_LUCKY_NUMBER,
FEATURE_TEACHERS,
FEATURE_SUBJECTS,
FEATURE_CLASSROOMS)
private fun getAllFeatures(): List<Int> = listOf(
FEATURE_TIMETABLE,
FEATURE_AGENDA,
FEATURE_GRADES,
FEATURE_HOMEWORK,
FEATURE_NOTICES,
FEATURE_ATTENDANCES,
FEATURE_MESSAGES_INBOX,
FEATURE_MESSAGES_OUTBOX,
FEATURE_ANNOUNCEMENTS)
fun getAllIds(): List<Int> = getAllFeatures() + getAllNecessary()
fun getIdsByView(targetId: Int): List<Int> {
return when (targetId) {
DRAWER_ITEM_HOME -> getAllFeatures()
DRAWER_ITEM_TIMETABLE -> listOf(FEATURE_TIMETABLE)
DRAWER_ITEM_AGENDA -> listOf(FEATURE_AGENDA)
DRAWER_ITEM_GRADES -> listOf(FEATURE_GRADES)
DRAWER_ITEM_MESSAGES -> when (MessagesFragment.pageSelection) {
TYPE_RECEIVED -> listOf(FEATURE_MESSAGES_INBOX)
TYPE_SENT -> listOf(FEATURE_MESSAGES_OUTBOX)
else -> listOf(FEATURE_MESSAGES_INBOX, FEATURE_MESSAGES_OUTBOX)
}
DRAWER_ITEM_HOMEWORKS -> listOf(FEATURE_HOMEWORK)
DRAWER_ITEM_NOTICES -> listOf(FEATURE_NOTICES)
DRAWER_ITEM_ATTENDANCES -> listOf(FEATURE_ATTENDANCES)
DRAWER_ITEM_ANNOUNCEMENTS -> listOf(FEATURE_ANNOUNCEMENTS)
else -> getAllFeatures()
} + getAllNecessary()
}
}

View File

@ -10,6 +10,8 @@ import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusMessages
import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusSynergia import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrusSynergia
import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod
val SYNERGIA_API_ENABLED = "true".toBoolean()
const val LOGIN_TYPE_MOBIDZIENNIK = 1 const val LOGIN_TYPE_MOBIDZIENNIK = 1
const val LOGIN_TYPE_LIBRUS = 2 const val LOGIN_TYPE_LIBRUS = 2
const val LOGIN_TYPE_IUCZNIOWIE = 3 const val LOGIN_TYPE_IUCZNIOWIE = 3
@ -37,14 +39,29 @@ const val LOGIN_METHOD_VULCAN_WEB = 100
const val LOGIN_METHOD_VULCAN_API = 200 const val LOGIN_METHOD_VULCAN_API = 200
val librusLoginMethods = listOf( val librusLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_PORTAL, null, LoginLibrusPortal::class.java) { _, _ -> LOGIN_METHOD_NOT_NEEDED }, LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_PORTAL, LoginLibrusPortal::class.java)
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_API, null, LoginLibrusApi::class.java) { _, loginStore -> .withIsPossible { _, loginStore ->
if (loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) LOGIN_METHOD_LIBRUS_PORTAL else LOGIN_METHOD_NOT_NEEDED loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL
}, }
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, listOf(FEATURE_GRADES, FEATURE_HOMEWORKS, FEATURE_MESSAGES_INBOX, FEATURE_MESSAGES_OUTBOX), LoginLibrusSynergia::class.java) { profile, _ -> .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED
}, LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_API, LoginLibrusApi::class.java)
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, listOf(FEATURE_MESSAGES_INBOX, FEATURE_MESSAGES_OUTBOX), LoginLibrusMessages::class.java) { profile, _ -> .withIsPossible { _, loginStore ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED loginStore.mode != LOGIN_MODE_LIBRUS_SYNERGIA || SYNERGIA_API_ENABLED
} }
.withRequiredLoginMethod { _, loginStore ->
if (loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) LOGIN_METHOD_LIBRUS_PORTAL else LOGIN_METHOD_NOT_NEEDED
},
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LoginLibrusSynergia::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED
},
LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, LoginLibrusMessages::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { profile, _ ->
if (profile?.hasStudentData("accountPassword") == false) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED
}
) )

View File

@ -0,0 +1,9 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
class SyncErrorEvent(val error: ApiError)

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events
class SyncFinishedEvent

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events
class SyncProfileFinishedEvent(val profileId: Int)

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events
class SyncProgressEvent(val profileId: Int, val profileName: String?, val progress: Int, val progressRes: Int?)

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events
class SyncStartedEvent(val profileId: Int)

View File

@ -0,0 +1,13 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events.requests
import pl.szczodrzynski.edziennik.api.v2.models.ApiTask
data class MessageGetRequest(override val profileId: Int, val messageId: Int) : ApiTask(profileId) {
override fun toString(): String {
return "MessageGetRequest(profileId=$profileId, messageId=$messageId)"
}
}

View File

@ -0,0 +1,13 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events.requests
import pl.szczodrzynski.edziennik.api.v2.models.ApiTask
data class SyncProfileRequest(override val profileId: Int, val featureIds: List<Int>?) : ApiTask(profileId) {
override fun toString(): String {
return "SyncProfileRequest(profileId=$profileId, featureIds=$featureIds)"
}
}

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.events.requests
class SyncRequest()

View File

@ -0,0 +1,13 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-29.
*/
package pl.szczodrzynski.edziennik.api.v2.events.requests
import pl.szczodrzynski.edziennik.api.v2.models.ApiTask
class SyncViewRequest(override val profileId: Int, val targetId: Int) : ApiTask(profileId) {
override fun toString(): String {
return "SyncViewRequest(profileId=$profileId, targetId=$targetId)"
}
}

View File

@ -0,0 +1,17 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-29.
*/
package pl.szczodrzynski.edziennik.api.v2.interfaces
import pl.szczodrzynski.edziennik.api.v2.models.Endpoint
import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod
/**
* A callback passed only to an e-register class.
* All [Endpoint]s and [LoginMethod]s receive this callback,
* but may only use [EndpointCallback]'s methods.
*/
interface EdziennikCallback : EndpointCallback {
fun onCompleted()
}

View File

@ -0,0 +1,10 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-29.
*/
package pl.szczodrzynski.edziennik.api.v2.interfaces
interface EdziennikInterface {
fun sync(featureIds: List<Int>)
fun getMessage(messageId: Int)
}

View File

@ -0,0 +1,18 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-29.
*/
package pl.szczodrzynski.edziennik.api.v2.interfaces
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.models.Endpoint
import pl.szczodrzynski.edziennik.api.v2.models.LoginMethod
/**
* A callback passed to all [Endpoint]s and [LoginMethod]s
*/
interface EndpointCallback {
fun onError(apiError: ApiError)
fun onProgress(step: Int)
fun onStartProgress(stringRes: Int)
}

View File

@ -1,20 +0,0 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-20.
*/
package pl.szczodrzynski.edziennik.api.v2.interfaces
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile
abstract class ILoginMethod(
val app: App,
val profile: Profile?,
val loginStore: LoginStore,
val callback: ProgressCallback,
val onSuccess: () -> Unit
) {
}

View File

@ -9,22 +9,30 @@ import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.AppError import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback
import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410 import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.datamodels.LoginStore import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile import pl.szczodrzynski.edziennik.datamodels.Profile
import pl.szczodrzynski.edziennik.datamodels.ProfileFull import pl.szczodrzynski.edziennik.datamodels.ProfileFull
class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: SyncCallback) { class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
val internalErrorList = mutableListOf<Int>() val internalErrorList = mutableListOf<Int>()
lateinit var data: DataLibrus val data: DataLibrus
init { init {
data = DataLibrus(app, profile, loginStore).apply { data = DataLibrus(app, profile, loginStore).apply {
callback = wrapCallback(this@Librus.callback) callback = wrapCallback(this@Librus.callback)
} }
data.satisfyLoginMethods()
}
override fun sync(featureIds: List<Int>) {
}
override fun getMessage(messageId: Int) {
} }

View File

@ -7,7 +7,6 @@ import pl.szczodrzynski.edziennik.api.interfaces.*
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginLibrus import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginSynergia import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginSynergia
import pl.szczodrzynski.edziennik.api.v2.librus.login.SynergiaTokenExtractor
import pl.szczodrzynski.edziennik.api.v2.models.Data import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.datamodels.LoginStore import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.MessageFull import pl.szczodrzynski.edziennik.datamodels.MessageFull
@ -15,10 +14,9 @@ import pl.szczodrzynski.edziennik.datamodels.Profile
import pl.szczodrzynski.edziennik.datamodels.ProfileFull import pl.szczodrzynski.edziennik.datamodels.ProfileFull
import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo
import pl.szczodrzynski.edziennik.models.Endpoint import pl.szczodrzynski.edziennik.models.Endpoint
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.lang.Exception import java.lang.Exception
class LibrusOld(val app: App, val profile: Profile?, val loginStore: LoginStore) : EdziennikInterface { class LibrusOld(val app: App, val profile: Profile?, val loginStore: LoginStore) : OldEdziennikInterface {
private val TAG = "librus.Librus" private val TAG = "librus.Librus"
lateinit var syncCallback: SyncCallback lateinit var syncCallback: SyncCallback

View File

@ -5,6 +5,7 @@
package pl.szczodrzynski.edziennik.api.v2.librus package pl.szczodrzynski.edziennik.api.v2.librus
import android.content.Context import android.content.Context
import android.content.Intent
import com.google.gson.JsonObject import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.AppError import pl.szczodrzynski.edziennik.api.AppError
@ -30,21 +31,24 @@ class LibrusTest(val app: App) {
putStudentData("accountLogin", "1234567") putStudentData("accountLogin", "1234567")
putStudentData("accountToken", "token") //putStudentData("accountToken", "token")
putStudentData("accountTokenTime", 1569523077) //putStudentData("accountTokenTime", 1569458277)
} }
val loginStore = LoginStore(1, LOGIN_TYPE_LIBRUS, JsonObject().apply { val loginStore = LoginStore(1, LOGIN_TYPE_LIBRUS, JsonObject().apply {
addProperty("email", "test@example.com") addProperty("email", "test@example.com")
addProperty("password", "zaq1@WSX") addProperty("password", "zaq1@WSX")
addProperty("accessToken", "token") //addProperty("accessToken", "token")
addProperty("refreshToken", "refresh") //addProperty("refreshToken", "refresh")
addProperty("tokenExpiryTime", 1569523077) //addProperty("tokenExpiryTime", 1569523077)
}).also { }).also {
it.mode = LOGIN_MODE_LIBRUS_EMAIL it.mode = LOGIN_MODE_LIBRUS_EMAIL
} }
fun go() { fun go() {
app.startService(Intent(app, ApiService::class.java))
val data = DataLibrus(app, profile, loginStore).apply { val data = DataLibrus(app, profile, loginStore).apply {
callback = object : ProgressCallback { callback = object : ProgressCallback {
override fun onProgress(progressStep: Int) { override fun onProgress(progressStep: Int) {

View File

@ -19,10 +19,11 @@ class LoginLibrus(val data: DataLibrus, vararg loginMethodIds: Int, val onSucces
init { init {
for (loginMethodId in loginMethodIds) { for (loginMethodId in loginMethodIds) {
var requiredLoginMethod = loginMethodId var requiredLoginMethod: Int? = loginMethodId
while (requiredLoginMethod != LOGIN_METHOD_NOT_NEEDED) { while (requiredLoginMethod != LOGIN_METHOD_NOT_NEEDED) {
librusLoginMethods.singleOrNull { it.loginMethodId == requiredLoginMethod }?.let { loginMethod -> librusLoginMethods.singleOrNull { it.loginMethodId == requiredLoginMethod }?.let { loginMethod ->
loginMethodList.add(requiredLoginMethod) if (requiredLoginMethod != null)
loginMethodList.add(requiredLoginMethod!!)
requiredLoginMethod = loginMethod.requiredLoginMethod(data.profile, data.loginStore) requiredLoginMethod = loginMethod.requiredLoginMethod(data.profile, data.loginStore)
} }
} }

View File

@ -9,14 +9,9 @@ import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.JsonCallbackHandler import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.api.AppError import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.AppError.*
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
import java.net.HttpURLConnection.* import java.net.HttpURLConnection.*
class LoginLibrusApi { class LoginLibrusApi {
@ -121,7 +116,7 @@ class LoginLibrusApi {
data.error(TAG, ERROR_RESPONSE_EMPTY, response) data.error(TAG, ERROR_RESPONSE_EMPTY, response)
return return
} }
json.getString("error")?.let { error -> if (response?.code() != 200) json.getString("error")?.let { error ->
when (error) { when (error) {
"librus_captcha_needed" -> ERROR_LOGIN_LIBRUS_API_CAPTCHA_NEEDED "librus_captcha_needed" -> ERROR_LOGIN_LIBRUS_API_CAPTCHA_NEEDED
"connection_problems" -> ERROR_LOGIN_LIBRUS_API_CONNECTION_PROBLEMS "connection_problems" -> ERROR_LOGIN_LIBRUS_API_CONNECTION_PROBLEMS
@ -140,7 +135,7 @@ class LoginLibrusApi {
try { try {
data.apiAccessToken = json.getString("access_token") data.apiAccessToken = json.getString("access_token")
data.apiRefreshToken = json.getString("refresh_token") data.apiRefreshToken = json.getString("refresh_token")
data.apiTokenExpiryTime = currentTimeUnix() + json.getInt("expires_in", 86400) data.apiTokenExpiryTime = response.getUnixDate() + json.getInt("expires_in", 86400)
onSuccess() onSuccess()
} catch (e: NullPointerException) { } catch (e: NullPointerException) {
data.error(TAG, EXCEPTION_LOGIN_LIBRUS_API_TOKEN, response, e, json) data.error(TAG, EXCEPTION_LOGIN_LIBRUS_API_TOKEN, response, e, json)

View File

@ -13,6 +13,7 @@ import okhttp3.internal.http.HttpDate
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.currentTimeUnix import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.getUnixDate
class LoginLibrusMessages(val data: DataLibrus, val onSuccess: () -> Unit) { class LoginLibrusMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
companion object { companion object {
@ -73,7 +74,7 @@ class LoginLibrusMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
return return
} }
data.messagesSessionId = sessionId data.messagesSessionId = sessionId
data.messagesSessionIdExpiryTime = currentTimeUnix() + 3600 /* 1h */ data.messagesSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
onSuccess() onSuccess()
} }

View File

@ -121,7 +121,52 @@ class LoginLibrusPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
private var refreshTokenFailed = false private var refreshTokenFailed = false
private fun accessToken(code: String?, refreshToken: String?) { private fun accessToken(code: String?, refreshToken: String?) {
data.callback.onActionStarted(R.string.sync_action_getting_token) val onSuccess = { json: JsonObject, response: Response? ->
data.portalAccessToken = json.getString("access_token")
data.portalRefreshToken = json.getString("refresh_token")
data.portalTokenExpiryTime = response.getUnixDate() + json.getInt("expires_in", 86400)
onSuccess()
}
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null) {
data.error(TAG, ERROR_RESPONSE_EMPTY, response)
return
}
val error = if (response?.code() == 200) null else
json.getString("hint")
error?.let { code ->
when (code) {
"Authorization code has expired" -> ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED
"Authorization code has been revoked" -> ERROR_LOGIN_LIBRUS_PORTAL_CODE_REVOKED
"Check the `client_id` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_CLIENT_ID
"Check the `code` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_CODE
"Check the `refresh_token` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_REFRESH
"Check the `redirect_uri` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_REDIRECT
else -> when (json.getString("error")) {
"unsupported_grant_type" -> ERROR_LOGIN_LIBRUS_PORTAL_UNSUPPORTED_GRANT
"invalid_client" -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID
else -> ERROR_LOGIN_LIBRUS_PORTAL_OTHER
}
}.let { errorCode ->
data.error(TAG, errorCode, apiResponse = json, response = response)
return
}
}
try {
onSuccess(json, response)
} catch (e: NullPointerException) {
data.error(TAG, EXCEPTION_LOGIN_LIBRUS_PORTAL_TOKEN, response, e, json)
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(TAG, ERROR_REQUEST_FAILURE, response, throwable)
}
}
val params = ArrayList<Pair<String, Any>>() val params = ArrayList<Pair<String, Any>>()
params.add(Pair("client_id", LIBRUS_CLIENT_ID)) params.add(Pair("client_id", LIBRUS_CLIENT_ID))
if (code != null) { if (code != null) {
@ -132,46 +177,14 @@ class LoginLibrusPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
params.add(Pair("grant_type", "refresh_token")) params.add(Pair("grant_type", "refresh_token"))
params.add(Pair("refresh_token", refreshToken)) params.add(Pair("refresh_token", refreshToken))
} }
Request.builder() Request.builder()
.url(LIBRUS_TOKEN_URL) .url(LIBRUS_TOKEN_URL)
.userAgent(LIBRUS_USER_AGENT) .userAgent(LIBRUS_USER_AGENT)
.addParams(params) .addParams(params)
.allowErrorCode(HTTP_UNAUTHORIZED)
.post() .post()
.callback(object : JsonCallbackHandler() { .allowErrorCode(HTTP_UNAUTHORIZED)
override fun onSuccess(json: JsonObject?, response: Response) { .callback(callback)
if (json == null) {
data.error(TAG, ERROR_RESPONSE_EMPTY, response)
return
}
json.getString("error")?.let { error ->
val hint = json.getString("hint", "")
//val message = json.getString("message", "")
if (!refreshTokenFailed && refreshToken != null && (hint == "Token has been revoked" || hint == "Token has expired")) {
c(TAG, "refreshing the token failed. Trying to log in again.")
refreshTokenFailed = true
authorize(LIBRUS_AUTHORIZE_URL)
return
}
data.error(TAG, ERROR_LOGIN_LIBRUS_PORTAL_TOKEN_ERROR, response, apiResponse = json)
return
}
try {
data.portalAccessToken = json.getString("access_token")
data.portalRefreshToken = json.getString("refresh_token")
data.portalTokenExpiryTime = currentTimeUnix() + json.getInt("expires_in", 86400)
onSuccess()
} catch (e: NullPointerException) {
data.error(TAG, EXCEPTION_LOGIN_LIBRUS_PORTAL_TOKEN, response, e, json)
}
}
override fun onFailure(response: Response, throwable: Throwable) {
data.error(TAG, ERROR_REQUEST_FAILURE, response, throwable)
}
})
.build() .build()
.enqueue() .enqueue()
} }

View File

@ -15,6 +15,7 @@ import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus import pl.szczodrzynski.edziennik.api.v2.librus.data.DataLibrus
import pl.szczodrzynski.edziennik.currentTimeUnix import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.isNotNullNorEmpty import pl.szczodrzynski.edziennik.isNotNullNorEmpty
import java.lang.Exception import java.lang.Exception
import java.net.HttpURLConnection import java.net.HttpURLConnection
@ -75,7 +76,10 @@ class LoginLibrusSynergia(val data: DataLibrus, val onSuccess: () -> Unit) {
data.error(TAG, ERROR_RESPONSE_EMPTY, response) data.error(TAG, ERROR_RESPONSE_EMPTY, response)
return return
} }
val error = json.getString("Code") ?: json.getString("Message") ?: response?.parserErrorBody val error = if (response?.code() == 200) null else
json.getString("Code") ?:
json.getString("Message") ?:
response?.parserErrorBody
error?.let { code -> error?.let { code ->
when (code) { when (code) {
"TokenIsExpired" -> ERROR_LIBRUS_API_TOKEN_EXPIRED "TokenIsExpired" -> ERROR_LIBRUS_API_TOKEN_EXPIRED
@ -145,7 +149,7 @@ class LoginLibrusSynergia(val data: DataLibrus, val onSuccess: () -> Unit) {
return return
} }
data.synergiaSessionId = sessionId data.synergiaSessionId = sessionId
data.synergiaSessionIdExpiryTime = currentTimeUnix() + 3600 /* 1h */ data.synergiaSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
onSuccess() onSuccess()
} }
else { else {

View File

@ -33,16 +33,80 @@ class SynergiaTokenExtractor(val data: DataLibrus, val onSuccess: () -> Unit) {
} }
else { else {
if (!synergiaAccount()) { if (!synergiaAccount()) {
data.error(TAG, ERROR_LOGIN_DATA_MISSING)
} }
} }
}} }}
/**
* Get an Api token from the Portal account, using Portal API.
* If necessary, refreshes the token.
*/
private fun synergiaAccount(): Boolean { private fun synergiaAccount(): Boolean {
val accountLogin = data.apiLogin ?: return false val accountLogin = data.apiLogin ?: return false
val accessToken = data.portalAccessToken ?: return false val accessToken = data.portalAccessToken ?: return false
data.callback.onActionStarted(R.string.sync_action_getting_account)
d(TAG, "Requesting " + (LIBRUS_ACCOUNT_URL + accountLogin)) val onSuccess = { json: JsonObject, response: Response? ->
// synergiaAccount is executed when a synergia token needs a refresh
val accountId = json.getInt("id")
val accountToken = json.getString("accessToken")
if (accountId == null || accountToken == null) {
data.error(TAG, ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_TOKEN_MISSING, response, apiResponse = json)
}
else {
data.apiAccessToken = accountToken
data.apiTokenExpiryTime = response.getUnixDate() + 6 * 60 * 60
// TODO remove this
data.profile?.studentNameLong = json.getString("studentName")
val nameParts = json.getString("studentName")?.split(" ")?.toTypedArray()
data.profile?.studentNameShort = nameParts?.get(0) + " " + nameParts?.get(1)?.get(0)
onSuccess()
}
}
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null) {
data.error(TAG, ERROR_RESPONSE_EMPTY, response)
return
}
val error = if (response?.code() == 200) null else
json.getString("reason") ?:
json.getString("message") ?:
json.getString("hint") ?:
json.getString("Code")
error?.let { code ->
when (code) {
"requires_an_action" -> ERROR_LIBRUS_PORTAL_SYNERGIA_DISCONNECTED
"Access token is invalid" -> ERROR_LIBRUS_PORTAL_TOKEN_EXPIRED
"ApiDisabled" -> ERROR_LIBRUS_PORTAL_API_DISABLED
"Account not found" -> ERROR_LIBRUS_PORTAL_SYNERGIA_NOT_FOUND
else -> ERROR_LIBRUS_PORTAL_OTHER
}.let { errorCode ->
data.error(TAG, errorCode, apiResponse = json, response = response)
return
}
}
if (response?.code() == HTTP_OK) {
try {
onSuccess(json, response)
} catch (e: NullPointerException) {
e.printStackTrace()
data.error(TAG, EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN, response, e, json)
}
} else {
data.error(TAG, ERROR_REQUEST_FAILURE, response, apiResponse = json)
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(TAG, ERROR_REQUEST_FAILURE, response, throwable)
}
}
Request.builder() Request.builder()
.url(LIBRUS_ACCOUNT_URL + accountLogin) .url(LIBRUS_ACCOUNT_URL + accountLogin)
.userAgent(LIBRUS_USER_AGENT) .userAgent(LIBRUS_USER_AGENT)
@ -53,59 +117,7 @@ class SynergiaTokenExtractor(val data: DataLibrus, val onSuccess: () -> Unit) {
.allowErrorCode(HTTP_UNAUTHORIZED) .allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_BAD_REQUEST) .allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_GONE) .allowErrorCode(HTTP_GONE)
.callback(object : JsonCallbackHandler() { .callback(callback)
override fun onSuccess(json: JsonObject?, response: Response) {
if (json == null) {
data.error(TAG, ERROR_RESPONSE_EMPTY, response)
return
}
if (response.code() == 410) {
val reason = json.get("reason")
if (reason != null && reason !is JsonNull && reason.asString == "requires_an_action") {
data.error(TAG, ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_DISCONNECTED, response, apiResponse = json)
return
}
data.error(TAG, ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_410, response, apiResponse = json)
return
}
if (json.get("message") != null) {
val message = json.get("message").asString
if (message == "Account not found") {
data.error(TAG, ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_NOT_FOUND, response, apiResponse = json)
return
}
data.error(TAG, ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_OTHER, response, apiResponse = json)
return
}
if (response.code() == HTTP_OK) {
try {
// synergiaAccount is executed when a synergia token needs a refresh
val accountId = json.getInt("id")
val accountToken = json.getString("accessToken")
if (accountId == null || accountToken == null) {
data.error(TAG, ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_TOKEN_MISSING, response, apiResponse = json)
return
}
data.apiAccessToken = accountToken
data.apiTokenExpiryTime = currentTimeUnix() + 6*60*60
data.profile?.studentNameLong = json.getString("studentName")
val nameParts = json.getString("studentName")?.split(" ")?.toTypedArray()
data.profile?.studentNameShort = nameParts?.get(0) + " " + nameParts?.get(1)?.get(0)
onSuccess()
} catch (e: NullPointerException) {
e.printStackTrace()
data.error(TAG, EXCEPTION_LOGIN_LIBRUS_PORTAL_SYNERGIA_TOKEN, response, e, json)
}
} else {
data.error(TAG, ERROR_REQUEST_FAILURE, response, apiResponse = json)
}
}
override fun onFailure(response: Response, throwable: Throwable) {
data.error(TAG, ERROR_REQUEST_FAILURE, response, throwable)
}
})
.build() .build()
.enqueue() .enqueue()
return true return true

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.models
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
class ApiError(val profileId: Int, val tag: String, val errorCode: Int) {
private var throwable: Throwable? = null
private var apiResponse: String? = null
private var request: Request? = null
private var response: Response? = null
fun withThrowable(throwable: Throwable?): ApiError {
this.throwable = throwable
return this
}
fun withApiResponse(apiResponse: String?): ApiError {
this.apiResponse = apiResponse
return this
}
fun withApiResponse(apiResponse: JsonObject?): ApiError {
this.apiResponse = apiResponse?.toString()
return this
}
fun withRequest(request: Request?): ApiError {
this.request = request
return this
}
fun withResponse(response: Response?): ApiError {
this.response = response
this.request = response?.request()
return this
}
}

View File

@ -1,6 +0,0 @@
package pl.szczodrzynski.edziennik.api.v2.models
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.datamodels.LoginStore
data class ApiLoginResult(val loginStore: LoginStore, val error: AppError?)

View File

@ -0,0 +1,9 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-9-28.
*/
package pl.szczodrzynski.edziennik.api.v2.models
open class ApiTask(open val profileId: Int) {
var taskId: Int = 0
}

View File

@ -7,15 +7,24 @@ import com.google.gson.JsonObject
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.AppError import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.api.AppError.*
import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EndpointCallback
import pl.szczodrzynski.edziennik.datamodels.* import pl.szczodrzynski.edziennik.datamodels.*
import pl.szczodrzynski.edziennik.models.Date import pl.szczodrzynski.edziennik.models.Date
import java.io.InterruptedIOException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import javax.net.ssl.SSLException
open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore) { open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore) {
var fakeLogin = false var fakeLogin = false
lateinit var callback: ProgressCallback /**
* A callback passed to all [Endpoint]s and [LoginMethod]s
*/
lateinit var callback: EndpointCallback
/** /**
* A list of [LoginMethod]s *already fulfilled* during this sync. * A list of [LoginMethod]s *already fulfilled* during this sync.
@ -152,9 +161,25 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
} }
fun error(tag: String, errorCode: Int, response: Response? = null, throwable: Throwable? = null, apiResponse: JsonObject? = null) { fun error(tag: String, errorCode: Int, response: Response? = null, throwable: Throwable? = null, apiResponse: JsonObject? = null) {
callback.onError(null, AppError(tag, 999, errorCode, response, throwable, apiResponse)) var code = when (throwable) {
is UnknownHostException, is SSLException, is InterruptedIOException -> CODE_NO_INTERNET
is SocketTimeoutException -> CODE_TIMEOUT
else -> when (response?.code()) {
400, 401, 424, 500, 503, 404 -> CODE_MAINTENANCE
else -> errorCode
}
}
callback.onError(ApiError(profile?.id ?: -1, tag, code).withResponse(response).withThrowable(throwable).withApiResponse(apiResponse))
} }
fun error(tag: String, errorCode: Int, response: Response? = null, apiResponse: String? = null) { fun error(tag: String, errorCode: Int, response: Response? = null, apiResponse: String? = null) {
callback.onError(null, AppError(tag, 999, errorCode, response, null, apiResponse)) var code = when (null) {
is UnknownHostException, is SSLException, is InterruptedIOException -> CODE_NO_INTERNET
is SocketTimeoutException -> CODE_TIMEOUT
else -> when (response?.code()) {
400, 401, 424, 500, 503, 404 -> CODE_MAINTENANCE
else -> errorCode
}
}
callback.onError(ApiError(profile?.id ?: -1, tag, code).withResponse(response).withApiResponse(apiResponse))
} }
} }

View File

@ -7,21 +7,18 @@ import pl.szczodrzynski.edziennik.datamodels.Profile
* A Endpoint descriptor class. * A Endpoint descriptor class.
* *
* The API runs appropriate endpoints in order to fulfill its * The API runs appropriate endpoints in order to fulfill its
* [Feature] list. * feature list.
* An endpoint may have its [LoginMethod] dependencies which will be * An endpoint may have its [LoginMethod] dependencies which will be
* satisfied by the API before the [endpointClass]'s constructor is invoked. * satisfied by the API before the [endpointClass]'s constructor is invoked.
* *
* @param loginType type of the e-register this endpoint handles * @param loginType type of the e-register this endpoint handles
* @param endpointId a unique ID of this endpoint * @param featureId a feature ID
* @param featureIds a [List] of [Feature]s (their IDs) this endpoint can download * @param endpointIds a [List] of [Endpoint]s that satisfy this feature ID
* May be null if no strict feature set is associated with this method. * @param requiredLoginMethod a required login method, which will have to be executed before this endpoint.
* @param endpointClass a [Class] which constructor will be invoked when a data download is needed
* @param requiredLoginMethod a lambda returning a required login method (which will be called before this). May differ depending on the [Profile] and/or [LoginStore].
*/ */
class Endpoint( class Endpoint(
val loginType: Int, val loginType: Int,
val endpointId: Int, val featureId: Int,
val featureIds: List<Int>?, val endpointIds: List<Int>,
val endpointClass: Class<*>, val requiredLoginMethod: Int
val requiredLoginMethod: (profile: Profile?, loginStore: LoginStore) -> Int
) )

View File

@ -1,11 +0,0 @@
package pl.szczodrzynski.edziennik.api.v2.models
data class Feature(val featureId: Int, val loginOptions: Map<Int, List<Int>>) {
init {
}
}

View File

@ -1,13 +0,0 @@
package pl.szczodrzynski.edziennik.api.v2.models
import pl.szczodrzynski.edziennik.api.v2.*
val Features = listOf(
Feature(FEATURE_TIMETABLE, mapOf(
LOGIN_TYPE_LIBRUS to listOf(
LOGIN_MODE_LIBRUS_EMAIL,
LOGIN_MODE_LIBRUS_SYNERGIA,
LOGIN_MODE_LIBRUS_JST
)
))
)

View File

@ -1,6 +0,0 @@
package pl.szczodrzynski.edziennik.api.v2.models
import pl.szczodrzynski.edziennik.api.AppError
import pl.szczodrzynski.edziennik.datamodels.Profile
data class FirstLoginResult(val profileList: ArrayList<Profile>, val error: AppError?)

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.api.v2.models package pl.szczodrzynski.edziennik.api.v2.models
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_NOT_NEEDED
import pl.szczodrzynski.edziennik.datamodels.LoginStore import pl.szczodrzynski.edziennik.datamodels.LoginStore
import pl.szczodrzynski.edziennik.datamodels.Profile import pl.szczodrzynski.edziennik.datamodels.Profile
@ -16,15 +17,30 @@ import pl.szczodrzynski.edziennik.datamodels.Profile
* *
* @param loginType type of the e-register this login method handles * @param loginType type of the e-register this login method handles
* @param loginMethodId a unique ID of this login method * @param loginMethodId a unique ID of this login method
* @param featureIds a [List] of [Feature]s (their IDs) this login method can provide access to
* May be null if no strict feature set is associated with this method.
* @param loginMethodClass a [Class] which constructor will be invoked when a log in is needed * @param loginMethodClass a [Class] which constructor will be invoked when a log in is needed
* @param requiredLoginMethod a lambda returning a required login method (which will be called before this). May differ depending on the [Profile] and/or [LoginStore]. * @param requiredLoginMethod a required login method (which will be called before this). May differ depending on the [Profile] and/or [LoginStore].
*/ */
class LoginMethod( class LoginMethod(
val loginType: Int, val loginType: Int,
val loginMethodId: Int, val loginMethodId: Int,
val featureIds: List<Int>?,
val loginMethodClass: Class<*>, val loginMethodClass: Class<*>,
val requiredLoginMethod: (profile: Profile?, loginStore: LoginStore) -> Int private var mIsPossible: ((profile: Profile?, loginStore: LoginStore) -> Boolean)? = null,
) private var mRequiredLoginMethod: ((profile: Profile?, loginStore: LoginStore) -> Int)? = null
) {
fun withIsPossible(isPossible: (profile: Profile?, loginStore: LoginStore) -> Boolean): LoginMethod {
this.mIsPossible = isPossible
return this
}
fun withRequiredLoginMethod(requiredLoginMethod: (profile: Profile?, loginStore: LoginStore) -> Int): LoginMethod {
this.mRequiredLoginMethod = requiredLoginMethod
return this
}
fun isPossible(profile: Profile?, loginStore: LoginStore): Boolean {
return mIsPossible?.invoke(profile, loginStore) ?: false
}
fun requiredLoginMethod(profile: Profile?, loginStore: LoginStore): Int {
return mRequiredLoginMethod?.invoke(profile, loginStore) ?: LOGIN_METHOD_NOT_NEEDED
}
}

View File

@ -25,7 +25,10 @@ public interface ProfileDao {
LiveData<ProfileFull> getById(int profileId); LiveData<ProfileFull> getById(int profileId);
@Query("SELECT profiles.*, loginStores.loginStoreType, loginStores.loginStoreData FROM profiles LEFT JOIN loginStores ON profiles.loginStoreId = loginStores.loginStoreId WHERE profileId = :profileId") @Query("SELECT profiles.*, loginStores.loginStoreType, loginStores.loginStoreData FROM profiles LEFT JOIN loginStores ON profiles.loginStoreId = loginStores.loginStoreId WHERE profileId = :profileId")
ProfileFull getByIdNow(int profileId); ProfileFull getFullByIdNow(int profileId);
@Query("SELECT* FROM profiles WHERE profileId = :profileId")
Profile getByIdNow(int profileId);
@Query("SELECT * FROM profiles WHERE profileId >= 0 ORDER BY profileId") @Query("SELECT * FROM profiles WHERE profileId >= 0 ORDER BY profileId")
LiveData<List<Profile>> getAll(); LiveData<List<Profile>> getAll();
@ -45,6 +48,9 @@ public interface ProfileDao {
@Query("SELECT * FROM profiles WHERE syncEnabled = 1 AND archived = 0 AND profileId >= 0 ORDER BY profileId") @Query("SELECT * FROM profiles WHERE syncEnabled = 1 AND archived = 0 AND profileId >= 0 ORDER BY profileId")
List<Profile> getProfilesForSyncNow(); List<Profile> getProfilesForSyncNow();
@Query("SELECT profileId FROM profiles WHERE syncEnabled = 1 AND archived = 0 AND profileId >= 0 ORDER BY profileId")
List<Integer> getIdsForSyncNow();
@Query("SELECT profileId FROM profiles WHERE profileId >= 0 ORDER BY profileId") @Query("SELECT profileId FROM profiles WHERE profileId >= 0 ORDER BY profileId")
List<Integer> getIdsNow(); List<Integer> getIdsNow();

View File

@ -425,7 +425,7 @@ public class EventManualDialog {
return; return;
this.app = _app; this.app = _app;
AsyncTask.execute(() -> { AsyncTask.execute(() -> {
this.profile = app.db.profileDao().getByIdNow(profileId); this.profile = app.db.profileDao().getFullByIdNow(profileId);
if (profile != null) { if (profile != null) {
((Activity) context).runOnUiThread(() -> { ((Activity) context).runOnUiThread(() -> {
actualShow(editingEvent, defaultDate, defaultTime, dialogType); actualShow(editingEvent, defaultDate, defaultTime, dialogType);

View File

@ -159,7 +159,7 @@ public class WidgetLuckyNumber extends AppWidgetProvider {
} }
} }
Profile profile = app.db.profileDao().getByIdNow(widgetConfig.profileId); Profile profile = app.db.profileDao().getFullByIdNow(widgetConfig.profileId);
IIcon icon = CommunityMaterial.Icon.cmd_emoticon_dead_outline; IIcon icon = CommunityMaterial.Icon.cmd_emoticon_dead_outline;
boolean noNumberText = false; boolean noNumberText = false;
if (profile == null) { if (profile == null) {