/* * ____.____ __.____ ___ _____ * | | |/ _| | \ / _ \ ______ ______ * | | < | | / / /_\ \\____ \\____ \ * /\__| | | \| | / / | \ |_> > |_> > * \________|____|__ \______/ \____|__ / __/| __/ * \/ \/|__| |__| * * 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.text.format.DateUtils; import android.util.Log; import org.voidsink.anewjkuapp.CourseMap; import org.voidsink.anewjkuapp.KusssContentContract; import org.voidsink.anewjkuapp.PreferenceWrapper; import org.voidsink.anewjkuapp.R; import org.voidsink.anewjkuapp.analytics.Analytics; import org.voidsink.anewjkuapp.calendar.CalendarUtils; import org.voidsink.anewjkuapp.kusss.Exam; import org.voidsink.anewjkuapp.kusss.KusssHandler; import org.voidsink.anewjkuapp.kusss.KusssHelper; import org.voidsink.anewjkuapp.kusss.Term; import org.voidsink.anewjkuapp.notification.NewExamNotification; import org.voidsink.anewjkuapp.notification.SyncNotification; import org.voidsink.anewjkuapp.provider.KusssContentProvider; 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 ImportExamTask implements Callable<Void> { private static final String TAG = ImportExamTask.class.getSimpleName(); private final long mSyncFromNow; 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[] EXAM_PROJECTION = new String[]{ KusssContentContract.Exam.COL_ID, KusssContentContract.Exam.COL_TERM, KusssContentContract.Exam.COL_COURSEID, KusssContentContract.Exam.COL_DTSTART, KusssContentContract.Exam.COL_DTEND, KusssContentContract.Exam.COL_LOCATION, KusssContentContract.Exam.COL_DESCRIPTION, KusssContentContract.Exam.COL_INFO, KusssContentContract.Exam.COL_IS_REGISTERED, KusssContentContract.Exam.COL_TITLE}; public static final int COLUMN_EXAM_ID = 0; public static final int COLUMN_EXAM_TERM = 1; public static final int COLUMN_EXAM_COURSEID = 2; public static final int COLUMN_EXAM_DTSTART = 3; public static final int COLUMN_EXAM_DTEND = 4; public static final int COLUMN_EXAM_LOCATION = 5; public static final int COLUMN_EXAM_DESCRIPTION = 6; public static final int COLUMN_EXAM_INFO = 7; public static final int COLUMN_EXAM_IS_REGISTERED = 8; public static final int COLUMN_EXAM_TITLE = 9; public ImportExamTask(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 ImportExamTask(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)); this.mSyncFromNow = System.currentTimeMillis(); } private void updateNotify(String string) { if (mUpdateNotification != null) { mUpdateNotification.update(string); } } private String getEventString(Exam exam) { return AppUtils.getEventString(exam.getDtStart().getTime(), exam.getDtEnd().getTime(), exam.getTitle()); } @Override public Void call() throws Exception { if (mProvider == null) { return null; } if (mShowProgress) { mUpdateNotification = new SyncNotification(mContext, R.string.notification_sync_exam); mUpdateNotification.show(mContext.getString(R.string.notification_sync_exam_loading)); } NewExamNotification mNewExamNotification = new NewExamNotification(mContext); try { Log.d(TAG, "setup connection"); updateNotify(mContext.getString(R.string.notification_sync_connect)); if (KusssHandler.getInstance().isAvailable(mContext, AppUtils.getAccountAuthToken(mContext, mAccount), AppUtils.getAccountName(mContext, mAccount), AppUtils.getAccountPassword(mContext, mAccount))) { updateNotify(mContext.getString(R.string.notification_sync_exam_loading)); List<Exam> exams; if (PreferenceWrapper.getNewExamsByCourseId(mContext)) { CourseMap courseMap = new CourseMap(mContext); List<Term> terms = KusssContentProvider.getTerms(mContext); Log.d(TAG, "load exams by courseId"); exams = KusssHandler.getInstance().getNewExamsByCourseId( mContext, courseMap.getCourses(), terms); } else { Log.d(TAG, "load exams"); exams = KusssHandler.getInstance() .getNewExams(mContext); } if (exams == null) { mSyncResult.stats.numParseExceptions++; } else { Map<String, Exam> examMap = new HashMap<>(); for (Exam exam : exams) { Exam old = examMap.put(KusssHelper.getExamKey(exam.getCourseId(), AppUtils.termToString(exam.getTerm()), exam.getDtStart().getTime()), exam); if (old != null) { Log.w(TAG, "exam alread loaded: " + KusssHelper.getExamKey(old.getCourseId(), AppUtils.termToString(old.getTerm()), old.getDtStart().getTime())); } } Log.d(TAG, String.format("got %s exams", exams.size())); updateNotify(mContext.getString(R.string.notification_sync_exam_updating)); ArrayList<ContentProviderOperation> batch = new ArrayList<>(); Uri examUri = KusssContentContract.Exam.CONTENT_URI; Cursor c = mProvider.query(examUri, EXAM_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 examId; String examTerm; String examCourseId; long examDtStart; long examDtEnd; String examLocation; while (c.moveToNext()) { examId = c.getInt(COLUMN_EXAM_ID); examTerm = c.getString(COLUMN_EXAM_TERM); examCourseId = c.getString(COLUMN_EXAM_COURSEID); examDtStart = c.getLong(COLUMN_EXAM_DTSTART); examDtEnd = c.getLong(COLUMN_EXAM_DTEND); examLocation = c .getString(COLUMN_EXAM_LOCATION); Exam exam = examMap.remove(KusssHelper.getExamKey(examCourseId, examTerm, examDtStart)); if (exam != null) { // Check to see if the entry needs to be // updated Uri existingUri = examUri .buildUpon() .appendPath( Integer.toString(examId)) .build(); Log.d(TAG, "Scheduling update: " + existingUri); if (!CalendarUtils.isSameDay( new Date(examDtStart), exam.getDtStart()) || !new Date(examDtEnd).equals(exam.getDtEnd()) || !examLocation.equals(exam.getLocation())) { mNewExamNotification.addUpdate(getEventString(exam)); } batch.add(ContentProviderOperation .newUpdate( KusssContentContract .asEventSyncAdapter( existingUri, mAccount.name, mAccount.type)) .withValue( KusssContentContract.Exam.COL_ID, Integer.toString(examId)) .withValues(KusssHelper.getExamContentValues(exam)) .build()); mSyncResult.stats.numUpdates++; } else if (examDtStart > mSyncFromNow - DateUtils.DAY_IN_MILLIS) { // Entry doesn't exist. Remove only newer // events from the database. Uri deleteUri = examUri .buildUpon() .appendPath( Integer.toString(examId)) .build(); Log.d(TAG, "Scheduling delete: " + deleteUri); batch.add(ContentProviderOperation .newDelete( KusssContentContract .asEventSyncAdapter( deleteUri, mAccount.name, mAccount.type)) .build()); mSyncResult.stats.numDeletes++; } } c.close(); for (Exam exam : examMap.values()) { batch.add(ContentProviderOperation .newInsert( KusssContentContract .asEventSyncAdapter( examUri, mAccount.name, mAccount.type)) .withValues(KusssHelper.getExamContentValues(exam)) .build()); Log.d(TAG, "Scheduling insert: " + exam.getTerm() + " " + exam.getCourseId()); mNewExamNotification.addInsert(getEventString(exam)); mSyncResult.stats.numInserts++; } if (batch.size() > 0) { updateNotify(mContext.getString(R.string.notification_sync_exam_saving)); Log.d(TAG, "Applying batch update"); mProvider.applyBatch(batch); Log.d(TAG, "Notify resolver"); mResolver .notifyChange( KusssContentContract.Exam.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(); } mNewExamNotification.show(); if (mReleaseProvider) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { mProvider.close(); } else { mProvider.release(); } } return null; } }