/*
* ____.____ __.____ ___ _____
* | | |/ _| | \ / _ \ ______ ______
* | | < | | / / /_\ \\____ \\____ \
* /\__| | | \| | / / | \ |_> > |_> >
* \________|____|__ \______/ \____|__ / __/| __/
* \/ \/|__| |__|
*
* Copyright (c) 2014-2015 Paul "Marunjar" Pretsch
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package org.voidsink.anewjkuapp.update;
import android.accounts.Account;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SyncResult;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import org.voidsink.anewjkuapp.KusssContentContract;
import org.voidsink.anewjkuapp.R;
import org.voidsink.anewjkuapp.analytics.Analytics;
import org.voidsink.anewjkuapp.kusss.Assessment;
import org.voidsink.anewjkuapp.kusss.AssessmentType;
import org.voidsink.anewjkuapp.kusss.Grade;
import org.voidsink.anewjkuapp.kusss.KusssHandler;
import org.voidsink.anewjkuapp.kusss.KusssHelper;
import org.voidsink.anewjkuapp.notification.AssessmentChangedNotification;
import org.voidsink.anewjkuapp.notification.SyncNotification;
import org.voidsink.anewjkuapp.utils.AppUtils;
import org.voidsink.anewjkuapp.utils.Consts;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
public class ImportAssessmentTask implements Callable<Void> {
private static final String TAG = ImportCourseTask.class.getSimpleName();
private ContentProviderClient mProvider;
private boolean mReleaseProvider = false;
private final Account mAccount;
private SyncResult mSyncResult;
private final Context mContext;
private final ContentResolver mResolver;
private boolean mShowProgress;
private SyncNotification mUpdateNotification;
public static final String[] ASSESSMENT_PROJECTION = new String[]{
KusssContentContract.Assessment.COL_ID,
KusssContentContract.Assessment.COL_TERM,
KusssContentContract.Assessment.COL_COURSEID,
KusssContentContract.Assessment.COL_DATE,
KusssContentContract.Assessment.COL_CURRICULA_ID,
KusssContentContract.Assessment.COL_TYPE,
KusssContentContract.Assessment.COL_GRADE,
KusssContentContract.Assessment.COL_TITLE,
KusssContentContract.Assessment.COL_CODE,
KusssContentContract.Assessment.COL_ECTS,
KusssContentContract.Assessment.COL_SWS};
// Constants representing column positions from PROJECTION.
public static final int COLUMN_ASSESSMENT_ID = 0;
public static final int COLUMN_ASSESSMENT_TERM = 1;
public static final int COLUMN_ASSESSMENT_COURSEID = 2;
public static final int COLUMN_ASSESSMENT_DATE = 3;
public static final int COLUMN_ASSESSMENT_CURRICULA_ID = 4;
public static final int COLUMN_ASSESSMENT_TYPE = 5;
public static final int COLUMN_ASSESSMENT_GRADE = 6;
public static final int COLUMN_ASSESSMENT_TITLE = 7;
public static final int COLUMN_ASSESSMENT_CODE = 8;
public static final int COLUMN_ASSESSMENT_ECTS = 9;
public static final int COLUMN_ASSESSMENT_SWS = 10;
public ImportAssessmentTask(Account account, Context context) {
this(account, null, null, null, null, context);
this.mProvider = context.getContentResolver()
.acquireContentProviderClient(
KusssContentContract.Exam.CONTENT_URI);
this.mReleaseProvider = true;
this.mSyncResult = new SyncResult();
this.mShowProgress = true;
}
public ImportAssessmentTask(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult,
Context context) {
this.mAccount = account;
this.mProvider = provider;
this.mSyncResult = syncResult;
this.mResolver = context.getContentResolver();
this.mContext = context;
this.mShowProgress = (extras != null && extras.getBoolean(Consts.SYNC_SHOW_PROGRESS, false));
}
private void updateNotify(String string) {
if (mUpdateNotification != null) {
mUpdateNotification.update(string);
}
}
@Override
public Void call() throws Exception {
if (mProvider == null) {
return null;
}
if (mShowProgress) {
mUpdateNotification = new SyncNotification(mContext,
R.string.notification_sync_assessment);
mUpdateNotification.show(mContext.getString(R.string.notification_sync_assessment_loading));
}
AssessmentChangedNotification mAssessmentChangeNotification = new AssessmentChangedNotification(mContext);
updateNotify(mContext.getString(R.string.notification_sync_connect));
try {
Log.d(TAG, "setup connection");
if (KusssHandler.getInstance().isAvailable(mContext,
AppUtils.getAccountAuthToken(mContext, mAccount),
AppUtils.getAccountName(mContext, mAccount),
AppUtils.getAccountPassword(mContext, mAccount))) {
updateNotify(mContext.getString(R.string.notification_sync_assessment_loading));
Log.d(TAG, "load assessments");
List<Assessment> assessments = KusssHandler.getInstance()
.getAssessments(mContext);
if (assessments == null) {
mSyncResult.stats.numParseExceptions++;
} else {
Map<String, Assessment> assessmentMap = new HashMap<>();
ArrayList<Assessment> possibleDuplicates = new ArrayList<>();
for (Assessment assessment : assessments) {
if (assessment.getAssessmentType().isDuplicatesPossible()) {
possibleDuplicates.add(assessment);
} else {
assessmentMap.put(KusssHelper.getAssessmentKey(assessment.getCode(), assessment.getCourseId(), assessment.getDate().getTime()), assessment);
}
}
Log.d(TAG, String.format("got %s assessments", assessments.size()));
updateNotify(mContext.getString(R.string.notification_sync_assessment_updating));
ArrayList<ContentProviderOperation> batch = new ArrayList<>();
Uri examUri = KusssContentContract.Assessment.CONTENT_URI;
Cursor c = mProvider.query(examUri, ASSESSMENT_PROJECTION, null,
null, null);
if (c == null) {
Log.w(TAG, "selection failed");
} else {
Log.d(TAG, "Found " + c.getCount()
+ " local entries. Computing merge solution...");
int _Id;
String assessmentCode;
String assessmentCourseId;
Date assessmentDate;
AssessmentType assessmentType;
Grade assessmentGrade;
while (c.moveToNext()) {
_Id = c.getInt(COLUMN_ASSESSMENT_ID);
assessmentCode = c.getString(COLUMN_ASSESSMENT_CODE);
assessmentDate = new Date(c.getLong(COLUMN_ASSESSMENT_DATE));
assessmentType = AssessmentType.parseAssessmentType(c
.getInt(COLUMN_ASSESSMENT_TYPE));
assessmentGrade = Grade.parseGradeType(c
.getInt(COLUMN_ASSESSMENT_GRADE));
assessmentCourseId = c.getString(COLUMN_ASSESSMENT_COURSEID);
if (assessmentType.isDuplicatesPossible()) {
// delete
Log.d(TAG,
"delete: "
+ KusssHelper.getAssessmentKey(assessmentCode, assessmentCourseId, assessmentDate.getTime()));
// duplicate possible. remove existing
Uri deleteUri = examUri
.buildUpon()
.appendPath(Integer.toString(_Id))
.build();
Log.d(TAG, "Scheduling delete: "
+ deleteUri);
batch.add(ContentProviderOperation
.newDelete(
KusssContentContract
.asEventSyncAdapter(
deleteUri,
mAccount.name,
mAccount.type))
.build());
mSyncResult.stats.numDeletes++;
} else {
Assessment assessment = assessmentMap.remove(KusssHelper.getAssessmentKey(assessmentCode, assessmentCourseId, assessmentDate.getTime()));
if (assessment != null) {
// Check to see if the entry needs to be updated
Uri existingUri = examUri.buildUpon()
.appendPath(Integer.toString(_Id))
.build();
Log.d(TAG, "Scheduling update: " + existingUri);
if (!assessmentType.equals(assessment.getAssessmentType())
|| !assessmentGrade.equals(assessment.getGrade())) {
mAssessmentChangeNotification
.addUpdate(String.format("%s: %s",
assessment.getTitle(),
mContext.getString(assessment
.getGrade()
.getStringResID())));
}
batch.add(ContentProviderOperation
.newUpdate(
KusssContentContract
.asEventSyncAdapter(
existingUri,
mAccount.name,
mAccount.type))
.withValue(
KusssContentContract.Assessment.COL_ID,
Integer.toString(_Id))
.withValues(KusssHelper.getAssessmentContentValues(assessment))
.build());
mSyncResult.stats.numUpdates++;
}
}
}
c.close();
for (Assessment assessment : assessmentMap.values()) {
batch.add(ContentProviderOperation
.newInsert(
KusssContentContract
.asEventSyncAdapter(
examUri,
mAccount.name,
mAccount.type))
.withValues(KusssHelper.getAssessmentContentValues(assessment))
.build());
Log.d(TAG, "Scheduling insert: " + assessment.getTerm()
+ " " + assessment.getCourseId());
mAssessmentChangeNotification.addInsert(String.format(
"%s: %s", assessment.getTitle(), mContext
.getString(assessment.getGrade()
.getStringResID())));
mSyncResult.stats.numInserts++;
}
for (Assessment assessment : possibleDuplicates) {
batch.add(ContentProviderOperation
.newInsert(
KusssContentContract
.asEventSyncAdapter(
examUri,
mAccount.name,
mAccount.type))
.withValues(KusssHelper.getAssessmentContentValues(assessment))
.build());
Log.d(TAG, "Scheduling insert: " + assessment.getTerm()
+ " " + assessment.getCourseId());
mSyncResult.stats.numInserts++;
}
if (batch.size() > 0) {
updateNotify(mContext.getString(R.string.notification_sync_assessment_saving));
Log.d(TAG, "Applying batch update");
mProvider.applyBatch(batch);
Log.d(TAG, "Notify resolver");
mResolver
.notifyChange(
KusssContentContract.Assessment.CONTENT_CHANGED_URI,
null, // No
// local
// observer
false); // IMPORTANT: Do not sync to
// network
} else {
Log.w(TAG, "No batch operations found! Do nothing");
}
}
}
KusssHandler.getInstance().logout(mContext);
} else {
mSyncResult.stats.numAuthExceptions++;
}
} catch (Exception e) {
Analytics.sendException(mContext, e, true);
Log.e(TAG, "import failed", e);
}
if (mUpdateNotification != null) {
mUpdateNotification.cancel();
}
mAssessmentChangeNotification.show();
if (mReleaseProvider) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
mProvider.close();
} else {
mProvider.release();
}
}
return null;
}
}