Delete lists and accounts in a transaction

* Delete google tasks when they're remotely cleared or deleted
* Delete caldav tasks when they're remotely deleted
pull/685/merge
Alex Baker 6 years ago
parent f0fd3fa944
commit fd79338c26

@ -10,8 +10,10 @@ import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
import android.support.test.runner.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskDeleter;
import javax.inject.Inject;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -22,6 +24,7 @@ import org.tasks.injection.TestComponent;
public class TaskDaoTests extends InjectingTestCase {
@Inject TaskDao taskDao;
@Inject TaskDeleter taskDeleter;
/** Test basic task creation, fetch, and save */
@Test
@ -113,8 +116,7 @@ public class TaskDaoTests extends InjectingTestCase {
assertEquals(1, taskDao.getAll().size());
// delete
long happyId = task.getId();
assertEquals(1, taskDao.deleteById(happyId));
taskDeleter.delete(task);
assertEquals(0, taskDao.getAll().size());
}
@ -138,7 +140,7 @@ public class TaskDaoTests extends InjectingTestCase {
assertNull(taskDao.fetch(1));
assertEquals(0, taskDao.deleteById(1));
taskDeleter.delete(ImmutableList.of(1L));
// make sure db still works
assertEquals(0, taskDao.getAll().size());

@ -9,6 +9,7 @@ import dagger.Module;
import dagger.Provides;
import org.tasks.data.AlarmDao;
import org.tasks.data.CaldavDao;
import org.tasks.data.DeletionDao;
import org.tasks.data.FilterDao;
import org.tasks.data.GoogleTaskDao;
import org.tasks.data.GoogleTaskListDao;
@ -101,6 +102,11 @@ public class TestModule {
return database.getFilterDao();
}
@Provides
public DeletionDao getDeletionDao(Database database) {
return database.getDeletionDao();
}
@Provides
public TaskAttachmentDao getTaskAttachmentDao(Database database) {
return database.getTaskAttachmentDao();

@ -21,7 +21,6 @@ public class GtaskListMaker {
lookup ->
new GoogleTaskList() {
{
setDeleted(0L);
setId(lookup.valueOf(GtaskListMaker.ID, 0L));
setAccount(lookup.valueOf(ACCOUNT, "account"));
setRemoteId(lookup.valueOf(REMOTE_ID, "1"));

@ -166,7 +166,7 @@ public class GtasksIndentActionTest extends InjectingTestCase {
googleTaskListDao.insert(account);
gtasksListService.updateLists(account, items);
storeList = googleTaskListDao.getActiveLists("account").get(0);
storeList = googleTaskListDao.getLists("account").get(0);
}
@Override

@ -88,7 +88,7 @@ public class GtasksListServiceTest extends InjectingTestCase {
assertEquals(
singletonList(newGtaskList(with(ID, 2L), with(REMOTE_ID, "2"))),
googleTaskListDao.getActiveLists("account"));
googleTaskListDao.getLists("account"));
}
@Test

@ -167,7 +167,7 @@ public class GtasksTaskListUpdaterTest extends InjectingTestCase {
}
private void createParentSiblingMaps() {
for (GoogleTaskList list : googleTaskListDao.getActiveLists("account")) {
for (GoogleTaskList list : googleTaskListDao.getLists("account")) {
gtasksTaskListUpdater.updateParentSiblingMapsFor(list);
}
}

@ -272,7 +272,7 @@ public class GtasksTaskMovingTest extends InjectingTestCase {
googleTaskListDao.insert(account);
gtasksListService.updateLists(account, items);
list = googleTaskListDao.getActiveLists("account").get(0);
list = googleTaskListDao.getLists("account").get(0);
}
@Override

@ -292,7 +292,6 @@ public final class TaskEditFragment extends InjectingFragment
.setPositiveButton(
android.R.string.ok,
(dialog, which) -> {
timerPlugin.stopTimer(model);
taskDeleter.markDeleted(model);
callback.taskEditFinished();
})

@ -97,6 +97,12 @@ public class AlarmService {
}
}
public void cancelAlarms(long taskId) {
for (Alarm alarm : getActiveAlarmsForTask(taskId)) {
jobs.cancelAlarm(alarm.getId());
}
}
/** Schedules alarms for a single task */
private void scheduleAlarms(long taskId) {
for (Alarm alarm : getActiveAlarmsForTask(taskId)) {

@ -18,6 +18,7 @@ import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao;
import org.tasks.data.CaldavTask;
import org.tasks.data.DeletionDao;
import org.tasks.data.Filter;
import org.tasks.data.FilterDao;
import org.tasks.data.GoogleTask;
@ -95,6 +96,8 @@ public abstract class Database extends RoomDatabase {
public abstract CaldavDao getCaldavDao();
public abstract DeletionDao getDeletionDao();
// --- implementation
public String getName() {

@ -8,7 +8,9 @@ package com.todoroo.astrid.dao;
import static com.todoroo.andlib.utility.DateUtilities.now;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Transaction;
import android.arch.persistence.room.Update;
import android.content.Context;
import android.database.Cursor;
@ -23,6 +25,8 @@ import com.todoroo.astrid.helper.UUIDHelper;
import java.util.ArrayList;
import java.util.List;
import org.tasks.BuildConfig;
import org.tasks.data.GoogleTaskAccount;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.LimitOffsetDataSource;
import org.tasks.jobs.AfterSaveIntentService;
import timber.log.Timber;
@ -58,7 +62,7 @@ public abstract class TaskDao {
@android.arch.persistence.room.Query("SELECT * FROM tasks WHERE _id IN (:taskIds)")
public abstract List<Task> fetch(List<Long> taskIds);
@android.arch.persistence.room.Query("SELECT COUNT(1) FROM tasks WHERE timerStart > 0")
@android.arch.persistence.room.Query("SELECT COUNT(1) FROM tasks WHERE timerStart > 0 AND deleted = 0")
public abstract int activeTimers();
@android.arch.persistence.room.Query(
@ -130,16 +134,6 @@ public abstract class TaskDao {
+ "WHERE completed > 0 AND calendarUri NOT NULL AND calendarUri != ''")
public abstract int clearCompletedCalendarEvents();
@android.arch.persistence.room.Query("SELECT * FROM tasks WHERE deleted > 0")
public abstract List<Task> getDeleted();
@android.arch.persistence.room.Query("DELETE FROM tasks WHERE _id = :id")
public abstract int deleteById(long id);
@android.arch.persistence.room.Query(
"SELECT tasks.* FROM tasks INNER JOIN google_tasks ON google_tasks.task = tasks._id WHERE google_tasks.deleted = 0 AND google_tasks.list_id = :googleTaskList")
public abstract List<Task> getGoogleTasks(String googleTaskList);
/**
* Saves the given task to the database.getDatabase(). Task must already exist. Returns true on
* success.

@ -7,12 +7,10 @@ package com.todoroo.astrid.gtasks;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.transform;
import static java.util.Collections.emptyList;
import android.support.v4.util.Pair;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.GtasksFilter;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.tasks.data.GoogleTaskAccount;
@ -41,7 +39,7 @@ public class GtasksFilterExposer {
public List<Pair<GoogleTaskAccount, List<Filter>>> getFilters() {
List<Pair<GoogleTaskAccount, List<Filter>>> listFilters = newArrayList();
for (GoogleTaskAccount account : googleTaskListDao.getAccounts()) {
List<GoogleTaskList> lists = googleTaskListDao.getActiveLists(account.getAccount());
List<GoogleTaskList> lists = googleTaskListDao.getLists(account.getAccount());
listFilters.add(new Pair<>(account, transform(lists, GtasksFilter::new)));
}
return listFilters;

@ -10,7 +10,6 @@ import static org.tasks.time.DateTimeUtils.printTimestamp;
import com.google.api.services.tasks.model.TaskList;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskDeleter;
import java.util.HashSet;
import java.util.List;
@ -55,7 +54,7 @@ public class GtasksListService {
* @param remoteLists remote information about your lists
*/
public synchronized void updateLists(GoogleTaskAccount account, List<TaskList> remoteLists) {
List<GoogleTaskList> lists = googleTaskListDao.getActiveLists(account.getAccount());
List<GoogleTaskList> lists = googleTaskListDao.getLists(account.getAccount());
Set<Long> previousLists = new HashSet<>();
for (GoogleTaskList list : lists) {
@ -96,20 +95,12 @@ public class GtasksListService {
// check for lists that aren't on remote server
for (Long listId : previousLists) {
deleteList(googleTaskListDao.getById(listId));
taskDeleter.delete(googleTaskListDao.getById(listId));
}
localBroadcastManager.broadcastRefreshList();
}
public void deleteList(GoogleTaskList gtasksList) {
for (Task task : taskDao.getGoogleTasks(gtasksList.getRemoteId())) {
taskDeleter.markDeleted(task);
}
googleTaskDao.deleteList(gtasksList.getRemoteId());
googleTaskListDao.deleteById(gtasksList.getId());
}
public List<GoogleTaskList> getListsToUpdate(List<TaskList> remoteLists) {
List<GoogleTaskList> listsToUpdate = newArrayList();
for (TaskList remoteList : remoteLists) {

@ -34,14 +34,6 @@ public class GtasksTaskContainer {
task.setCreationDate(DateUtilities.now());
task.setCompletionDate(
GtasksApiUtilities.gtasksCompletedTimeToUnixTime(remoteTask.getCompleted()));
if (remoteTask.getDeleted() == null || !remoteTask.getDeleted()) {
task.setDeletionDate(0L);
} else {
task.setDeletionDate(DateUtilities.now());
}
if (remoteTask.getHidden() != null && remoteTask.getHidden()) {
task.setDeletionDate(DateUtilities.now());
}
long dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.getDue());
mergeDates(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate), task);

@ -62,6 +62,10 @@ public final class ReminderService {
}
}
public void cancelReminder(long taskId) {
jobs.cancelReminder(taskId);
}
public void scheduleAlarm(Task task) {
if (task == null || !task.isSaved()) {
return;
@ -71,7 +75,7 @@ public final class ReminderService {
// Make sure no alarms are scheduled other than the next one. When that one is shown, it
// will schedule the next one after it, and so on and so forth.
jobs.cancelReminder(taskId);
cancelReminder(taskId);
if (task.isCompleted() || task.isDeleted()) {
return;

@ -5,81 +5,67 @@ import static com.todoroo.andlib.utility.DateUtilities.now;
import static com.todoroo.astrid.dao.TaskDao.TaskCriteria.isVisible;
import static com.todoroo.astrid.dao.TaskDao.TaskCriteria.notCompleted;
import com.google.common.collect.ImmutableList;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.tasks.calendars.CalendarEventProvider;
import org.tasks.data.AlarmDao;
import org.tasks.data.CaldavDao;
import org.tasks.data.GoogleTaskDao;
import org.tasks.data.LocationDao;
import org.tasks.data.TagDao;
import org.tasks.LocalBroadcastManager;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.DeletionDao;
import org.tasks.data.GoogleTaskAccount;
import org.tasks.data.GoogleTaskList;
import org.tasks.jobs.JobManager;
public class TaskDeleter {
private final JobManager jobManager;
private final TaskDao taskDao;
private final CalendarEventProvider calendarEventProvider;
private final AlarmDao alarmDao;
private final LocationDao locationDao;
private final TagDao tagDao;
private final GoogleTaskDao googleTaskDao;
private final CaldavDao caldavDao;
private final LocalBroadcastManager localBroadcastManager;
private final DeletionDao deletionDao;
@Inject
public TaskDeleter(
TaskDao taskDao,
CalendarEventProvider calendarEventProvider,
AlarmDao alarmDao,
LocationDao locationDao,
TagDao tagDao,
GoogleTaskDao googleTaskDao,
CaldavDao caldavDao) {
public TaskDeleter(DeletionDao deletionDao, JobManager jobManager, TaskDao taskDao, LocalBroadcastManager localBroadcastManager) {
this.deletionDao = deletionDao;
this.jobManager = jobManager;
this.taskDao = taskDao;
this.calendarEventProvider = calendarEventProvider;
this.alarmDao = alarmDao;
this.locationDao = locationDao;
this.tagDao = tagDao;
this.googleTaskDao = googleTaskDao;
this.caldavDao = caldavDao;
this.localBroadcastManager = localBroadcastManager;
}
public int purgeDeleted() {
List<Task> deleted = taskDao.getDeleted();
for (Task task : deleted) {
calendarEventProvider.deleteEvent(task);
long id = task.getId();
taskDao.deleteById(id);
alarmDao.deleteByTaskId(id);
locationDao.deleteByTaskId(id);
tagDao.deleteByTaskId(id);
googleTaskDao.deleteByTaskId(id);
caldavDao.deleteById(id);
}
List<Long> deleted = deletionDao.getDeleted();
deletionDao.delete(deleted);
return deleted.size();
}
public void markDeleted(Task item) {
if (!item.isSaved()) {
return;
}
item.setDeletionDate(now());
taskDao.save(item);
markDeleted(ImmutableList.of(item.getId()));
}
public List<Task> markDeleted(List<Long> taskIds) {
List<Task> tasks = taskDao.fetch(taskIds);
for (Task task : tasks) {
markDeleted(task);
}
deletionDao.markDeleted(now(), taskIds);
jobManager.cleanup(taskIds);
jobManager.syncNow();
localBroadcastManager.broadcastRefresh();
return tasks;
}
public void delete(Task task) {
delete(ImmutableList.of(task.getId()));
}
public void delete(List<Long> tasks) {
deletionDao.delete(tasks);
jobManager.cleanup(tasks);
localBroadcastManager.broadcastRefresh();
}
public int clearCompleted(Filter filter) {
List<Task> completed = new ArrayList<>();
List<Long> completed = new ArrayList<>();
String query =
filter
.getSqlQuery()
@ -87,12 +73,38 @@ public class TaskDeleter {
.replace(notCompleted().toString(), all.toString());
for (Task task : taskDao.fetchFiltered(query)) {
if (task.isCompleted()) {
completed.add(task);
completed.add(task.getId());
}
}
for (Task task : completed) {
markDeleted(task);
}
markDeleted(completed);
return completed.size();
}
public void delete(GoogleTaskList googleTaskList) {
List<Long> ids = deletionDao.delete(googleTaskList);
jobManager.cleanup(ids);
localBroadcastManager.broadcastRefresh();
localBroadcastManager.broadcastRefreshList();
}
public void delete(GoogleTaskAccount googleTaskAccount) {
List<Long> ids = deletionDao.delete(googleTaskAccount);
jobManager.cleanup(ids);
localBroadcastManager.broadcastRefresh();
localBroadcastManager.broadcastRefreshList();
}
public void delete(CaldavCalendar caldavCalendar) {
List<Long> ids = deletionDao.delete(caldavCalendar);
jobManager.cleanup(ids);
localBroadcastManager.broadcastRefresh();
localBroadcastManager.broadcastRefreshList();
}
public void delete(CaldavAccount caldavAccount) {
List<Long> ids = deletionDao.delete(caldavAccount);
jobManager.cleanup(ids);
localBroadcastManager.broadcastRefresh();
localBroadcastManager.broadcastRefreshList();
}
}

@ -52,7 +52,6 @@ public class TaskDuplicator {
clone.setCreationDate(now());
clone.setModificationDate(now());
clone.setCompletionDate(0L);
clone.setDeletionDate(0L);
clone.setCalendarUri("");
clone.setUuid(UUIDHelper.newUUID());

@ -10,6 +10,7 @@ import static java.util.Collections.emptyList;
import android.content.Context;
import android.content.res.Resources;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.QueryTemplate;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.dao.TaskDao;
@ -39,7 +40,9 @@ public final class TimerFilterExposer {
Resources r = context.getResources();
Filter filter =
new Filter(
r.getString(R.string.TFE_workingOn), new QueryTemplate().where(Task.TIMER_START.gt(0)));
r.getString(R.string.TFE_workingOn),
new QueryTemplate()
.where(Criterion.and(Task.TIMER_START.gt(0), Task.DELETION_DATE.eq(0))));
filter.icon = R.drawable.ic_timer_24dp;
return filter;
}

@ -80,7 +80,7 @@ public class TimerPlugin {
updateNotifications();
}
private void updateNotifications() {
public void updateNotifications() {
int count = taskDao.activeTimers();
if (count == 0) {
notificationManager.cancel(Constants.NOTIFICATION_TIMER);

@ -23,6 +23,7 @@ import com.google.api.services.tasks.model.TaskList;
import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.api.GtasksFilter;
import com.todoroo.astrid.gtasks.GtasksListService;
import com.todoroo.astrid.service.TaskDeleter;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.analytics.Tracker;
@ -63,6 +64,7 @@ public class GoogleTaskListSettingsActivity extends ThemedInjectingAppCompatActi
@Inject Tracker tracker;
@Inject ThemeCache themeCache;
@Inject ThemeColor themeColor;
@Inject TaskDeleter taskDeleter;
@BindView(R.id.name)
TextInputEditText name;
@ -273,7 +275,7 @@ public class GoogleTaskListSettingsActivity extends ThemedInjectingAppCompatActi
@Override
public void onListDeleted() {
tracker.reportEvent(Tracking.Events.GTASK_DELETE_LIST);
gtasksListService.deleteList(gtasksList);
taskDeleter.delete(gtasksList);
setResult(RESULT_OK, new Intent(ACTION_DELETED));
finish();
}

@ -10,6 +10,7 @@ import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavTask;
import org.tasks.data.Filter;
import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskAccount;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.Location;
import org.tasks.data.Tag;
@ -23,6 +24,7 @@ class BackupContainer {
final List<TagData> tags;
final List<Filter> filters;
final List<GoogleTaskList> googleTaskLists;
private final List<GoogleTaskAccount> googleTaskAccounts;
private final List<CaldavAccount> caldavAccounts;
private final List<CaldavCalendar> caldavCalendars;
@ -30,12 +32,14 @@ class BackupContainer {
List<TaskBackup> tasks,
List<TagData> tags,
List<Filter> filters,
List<GoogleTaskAccount> googleTaskAccounts,
List<GoogleTaskList> googleTaskLists,
List<CaldavAccount> caldavAccounts,
List<CaldavCalendar> caldavCalendars) {
this.tasks = tasks;
this.tags = tags;
this.filters = filters;
this.googleTaskAccounts = googleTaskAccounts;
this.googleTaskLists = googleTaskLists;
this.caldavAccounts = caldavAccounts;
this.caldavCalendars = caldavCalendars;
@ -49,6 +53,10 @@ class BackupContainer {
return caldavCalendars == null ? emptyList() : caldavCalendars;
}
public List<GoogleTaskAccount> getGoogleTaskAccounts() {
return googleTaskAccounts == null ? emptyList() : googleTaskAccounts;
}
static class TaskBackup {
final Task task;

@ -178,7 +178,8 @@ public class TasksJsonExporter {
taskBackups,
tagDataDao.getAll(),
filterDao.getAll(),
googleTaskListDao.getAll(),
googleTaskListDao.getAccounts(),
googleTaskListDao.getAllLists(),
caldavDao.getAccounts(),
caldavDao.getCalendars()));

@ -25,6 +25,7 @@ import org.tasks.data.CaldavTask;
import org.tasks.data.Filter;
import org.tasks.data.FilterDao;
import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskAccount;
import org.tasks.data.GoogleTaskDao;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.GoogleTaskListDao;
@ -132,6 +133,11 @@ public class TasksJsonImporter {
tagDataDao.createNew(tagData);
}
}
for (GoogleTaskAccount googleTaskAccount : backupContainer.getGoogleTaskAccounts()) {
if (googleTaskListDao.getAccount(googleTaskAccount.getAccount()) == null) {
googleTaskListDao.insert(googleTaskAccount);
}
}
for (GoogleTaskList googleTaskList : backupContainer.googleTaskLists) {
if (googleTaskListDao.getByRemoteId(googleTaskList.getRemoteId()) == null) {
googleTaskListDao.insert(googleTaskList);

@ -384,13 +384,7 @@ public class CaldavAccountSettingsActivity extends ThemedInjectingAppCompatActiv
.setPositiveButton(
R.string.remove,
(dialog, which) -> {
for (CaldavCalendar calendar :
caldavDao.getCalendarsByAccount(caldavAccount.getUuid())) {
taskDeleter.markDeleted(caldavDao.getTasksByCalendar(calendar.getUuid()));
caldavDao.deleteTasksForCalendar(calendar.getUuid());
}
caldavDao.deleteCalendarsForAccount(caldavAccount.getUuid());
caldavDao.delete(caldavAccount);
taskDeleter.delete(caldavAccount);
tracker.reportEvent(Events.CALDAV_ACCOUNT_REMOVED);
setResult(RESULT_OK);
finish();

@ -371,9 +371,7 @@ public class CaldavCalendarSettingsActivity extends ThemedInjectingAppCompatActi
}
private void onDeleted() {
taskDeleter.markDeleted(caldavDao.getTasksByCalendar(caldavCalendar.getUuid()));
caldavDao.deleteTasksForCalendar(caldavCalendar.getUuid());
caldavDao.delete(caldavCalendar);
taskDeleter.delete(caldavCalendar);
tracker.reportEvent(Events.CALDAV_LIST_DELETED);
setResult(RESULT_OK, new Intent(ACTION_DELETED));
finish();

@ -49,6 +49,7 @@ import okhttp3.ResponseBody;
import org.tasks.BuildConfig;
import org.tasks.LocalBroadcastManager;
import org.tasks.R;
import org.tasks.billing.Inventory;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao;
@ -70,6 +71,7 @@ public class CaldavSynchronizer {
private final TaskCreator taskCreator;
private final TaskDeleter taskDeleter;
private final Encryption encryption;
private final Inventory inventory;
private final Context context;
@Inject
@ -80,7 +82,8 @@ public class CaldavSynchronizer {
LocalBroadcastManager localBroadcastManager,
TaskCreator taskCreator,
TaskDeleter taskDeleter,
Encryption encryption) {
Encryption encryption,
Inventory inventory) {
this.context = context;
this.caldavDao = caldavDao;
this.taskDao = taskDao;
@ -88,12 +91,19 @@ public class CaldavSynchronizer {
this.taskCreator = taskCreator;
this.taskDeleter = taskDeleter;
this.encryption = encryption;
this.inventory = inventory;
}
public void sync() {
// required for dav4android (ServiceLoader)
Thread.currentThread().setContextClassLoader(context.getClassLoader());
for (CaldavAccount account : caldavDao.getAccounts()) {
if (!inventory.hasPro()) {
account.setError(context.getString(R.string.requires_pro_subscription));
caldavDao.update(account);
localBroadcastManager.broadcastRefreshList();
continue;
}
if (isNullOrEmpty(account.getPassword())) {
account.setError(context.getString(R.string.password_required));
caldavDao.update(account);
@ -114,11 +124,9 @@ public class CaldavSynchronizer {
}
Set<String> urls = newHashSet(transform(resources, c -> c.getLocation().toString()));
Timber.d("Found calendars: %s", urls);
for (CaldavCalendar deleted :
for (CaldavCalendar calendar :
caldavDao.findDeletedCalendars(account.getUuid(), newArrayList(urls))) {
taskDeleter.markDeleted(caldavDao.getTasksByCalendar(deleted.getUuid()));
caldavDao.deleteTasksForCalendar(deleted.getUuid());
caldavDao.delete(deleted);
taskDeleter.delete(calendar);
}
for (DavResource resource : resources) {
String url = resource.getLocation().toString();
@ -239,8 +247,7 @@ public class CaldavSynchronizer {
newHashSet(remoteObjects)));
if (deleted.size() > 0) {
Timber.d("DELETED %s", deleted);
taskDeleter.markDeleted(caldavDao.getTasks(caldavCalendar.getUuid(), deleted));
caldavDao.deleteObjects(caldavCalendar.getUuid(), deleted);
taskDeleter.delete(caldavDao.getTasks(caldavCalendar.getUuid(), deleted));
}
caldavCalendar.setCtag(remoteCtag);

@ -29,7 +29,4 @@ public interface AlarmDao {
@Insert
long insert(Alarm alarm);
@Query("DELETE FROM alarms WHERE task = :taskId")
void deleteByTaskId(long taskId);
}

@ -25,18 +25,12 @@ public interface CaldavDao {
@Update
void update(CaldavAccount caldavAccount);
@Delete
void delete(CaldavAccount caldavAccount);
@Insert
long insert(CaldavCalendar caldavCalendar);
@Update
void update(CaldavCalendar caldavCalendar);
@Delete
void delete(CaldavCalendar caldavCalendar);
@Insert
long insert(CaldavTask caldavTask);
@ -55,19 +49,13 @@ public interface CaldavDao {
@Query("SELECT * FROM caldav_tasks WHERE calendar = :calendar AND object = :object LIMIT 1")
CaldavTask getTask(String calendar, String object);
@Query("DELETE FROM caldav_tasks WHERE task = :taskId")
void deleteById(long taskId);
@Query("SELECT * FROM caldav_tasks WHERE task = :taskId")
List<CaldavTask> getTasks(long taskId);
@Query("SELECT task FROM caldav_tasks WHERE calendar = :calendar AND deleted = 0")
List<Long> getTasksByCalendar(String calendar);
@Query("SELECT * FROM caldav_calendar")
List<CaldavCalendar> getCalendars();
@Query("SELECT * FROM caldav_calendar WHERE account = :account")
@Query("SELECT * FROM caldav_calendar WHERE account = :account ORDER BY name")
List<CaldavCalendar> getCalendarsByAccount(String account);
@Query("SELECT * FROM caldav_calendar WHERE uuid = :uuid LIMIT 1")
@ -77,21 +65,12 @@ public interface CaldavDao {
"SELECT * FROM caldav_calendar WHERE account = :account AND name = :name COLLATE NOCASE LIMIT 1")
CaldavCalendar getCalendar(String account, String name);
@Query("DELETE FROM caldav_calendar WHERE account = :account")
void deleteCalendarsForAccount(String account);
@Query("DELETE FROM caldav_tasks WHERE calendar = :calendar")
void deleteTasksForCalendar(String calendar);
@Query("SELECT object FROM caldav_tasks WHERE calendar = :calendar")
List<String> getObjects(String calendar);
@Query("SELECT task FROM caldav_tasks WHERE calendar = :calendar AND object IN (:objects)")
List<Long> getTasks(String calendar, List<String> objects);
@Query("DELETE FROM caldav_tasks WHERE calendar = :calendar AND object IN (:objects)")
void deleteObjects(String calendar, List<String> objects);
@Query("SELECT * FROM caldav_calendar WHERE account = :account AND url NOT IN (:urls)")
List<CaldavCalendar> findDeletedCalendars(String account, List<String> urls);

@ -0,0 +1,105 @@
package org.tasks.data;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Transaction;
import java.util.ArrayList;
import java.util.List;
@Dao
public abstract class DeletionDao {
@Query("SELECT _id FROM tasks WHERE deleted > 0")
public abstract List<Long> getDeleted();
@Query("DELETE FROM caldav_tasks WHERE task IN(:ids)")
abstract void deleteCaldavTasks(List<Long> ids);
@Query("DELETE FROM google_tasks WHERE task IN(:ids)")
abstract void deleteGoogleTasks(List<Long> ids);
@Query("DELETE FROM tags WHERE task IN(:ids)")
abstract void deleteTags(List<Long> ids);
@Query("DELETE FROM locations WHERE task IN(:ids)")
abstract void deleteGeofences(List<Long> ids);
@Query("DELETE FROM alarms WHERE task IN(:ids)")
abstract void deleteAlarms(List<Long> ids);
@Query("DELETE FROM tasks WHERE _id IN(:ids)")
abstract void deleteTasks(List<Long> ids);
@Transaction
public void delete(List<Long> ids) {
deleteAlarms(ids);
deleteGeofences(ids);
deleteTags(ids);
deleteGoogleTasks(ids);
deleteCaldavTasks(ids);
deleteTasks(ids);
}
@Query("SELECT task FROM google_tasks WHERE deleted = 0 AND list_id = :listId")
abstract List<Long> getActiveGoogleTasks(String listId);
@Delete
abstract void deleteGoogleTaskList(GoogleTaskList googleTaskList);
@Transaction
public List<Long> delete(GoogleTaskList googleTaskList) {
List<Long> tasks = getActiveGoogleTasks(googleTaskList.getRemoteId());
delete(tasks);
deleteGoogleTaskList(googleTaskList);
return tasks;
}
@Delete
abstract void deleteGoogleTaskAccount(GoogleTaskAccount googleTaskAccount);
@Query("SELECT * FROM google_task_lists WHERE account = :account ORDER BY title ASC")
abstract List<GoogleTaskList> getLists(String account);
@Transaction
public List<Long> delete(GoogleTaskAccount googleTaskAccount) {
List<Long> deleted = new ArrayList<>();
for (GoogleTaskList list : getLists(googleTaskAccount.getAccount())) {
deleted.addAll(delete(list));
}
deleteGoogleTaskAccount(googleTaskAccount);
return deleted;
}
@Query("UPDATE tasks SET deleted = :timestamp WHERE _id IN(:ids)")
public abstract void markDeleted(long timestamp, List<Long> ids);
@Query("SELECT task FROM caldav_tasks WHERE calendar = :calendar AND deleted = 0")
abstract List<Long> getActiveCaldavTasks(String calendar);
@Delete
abstract void deleteCaldavCalendar(CaldavCalendar caldavCalendar);
@Transaction
public List<Long> delete(CaldavCalendar caldavCalendar) {
List<Long> tasks = getActiveCaldavTasks(caldavCalendar.getUuid());
delete(tasks);
deleteCaldavCalendar(caldavCalendar);
return tasks;
}
@Query("SELECT * FROM caldav_calendar WHERE account = :account")
abstract List<CaldavCalendar> getCalendars(String account);
@Delete
abstract void deleteCaldavAccount(CaldavAccount caldavAccount);
@Transaction
public List<Long> delete(CaldavAccount caldavAccount) {
List<Long> deleted = new ArrayList<>();
for (CaldavCalendar calendar : getCalendars(caldavAccount.getUuid())) {
deleted.addAll(delete(calendar));
}
deleteCaldavAccount(caldavAccount);
return deleted;
}
}

@ -10,9 +10,6 @@ import java.util.List;
@Dao
public interface GoogleTaskDao {
@Query("DELETE FROM google_tasks WHERE list_id = :remoteId")
void deleteList(String remoteId);
@Insert
void insert(GoogleTask task);
@ -34,9 +31,6 @@ public interface GoogleTaskDao {
"SELECT * FROM google_tasks WHERE list_id = :listId AND `order` < :startAtOrder ORDER BY `order` DESC")
List<GoogleTask> getTasksFromReverse(String listId, long startAtOrder);
@Query("DELETE FROM google_tasks WHERE task = :taskId")
void deleteByTaskId(long taskId);
@Delete
void delete(GoogleTask deleted);

@ -43,7 +43,8 @@ public class GoogleTaskList implements Parcelable {
private long lastSync;
@ColumnInfo(name = "deleted")
private long deleted;
@Deprecated
private long deleted = 0;
@ColumnInfo(name = "color")
private Integer color;

@ -5,57 +5,46 @@ import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Transaction;
import android.arch.persistence.room.Update;
import java.util.List;
@Dao
public interface GoogleTaskListDao {
public abstract class GoogleTaskListDao {
@Query("SELECT * FROM google_task_accounts")
List<GoogleTaskAccount> getAccounts();
public abstract List<GoogleTaskAccount> getAccounts();
@Query("SELECT * FROM google_task_accounts WHERE account = :account COLLATE NOCASE LIMIT 1")
GoogleTaskAccount getAccount(String account);
public abstract GoogleTaskAccount getAccount(String account);
@Query("SELECT * FROM google_task_lists WHERE _id = :id")
GoogleTaskList getById(long id);
public abstract GoogleTaskList getById(long id);
@Query("SELECT * FROM google_task_lists WHERE account = :account AND deleted = 0 ORDER BY title ASC")
List<GoogleTaskList> getActiveLists(String account);
@Query("SELECT * FROM google_task_lists WHERE account = :account ORDER BY title ASC")
public abstract List<GoogleTaskList> getLists(String account);
@Query("SELECT * FROM google_task_lists WHERE remote_id = :remoteId LIMIT 1")
GoogleTaskList getByRemoteId(String remoteId);
public abstract GoogleTaskList getByRemoteId(String remoteId);
@Query("SELECT * FROM google_task_lists WHERE remote_id = :remoteId AND IFNULL(account, '') = '' LIMIT 1")
GoogleTaskList findExistingList(String remoteId);
public abstract GoogleTaskList findExistingList(String remoteId);
@Query("SELECT * FROM google_task_lists")
List<GoogleTaskList> getAll();
@Query("SELECT * FROM google_task_lists WHERE deleted = 0")
List<GoogleTaskList> getAllActiveLists();
@Query("DELETE FROM google_task_lists WHERE _id = :id")
void deleteById(long id);
public abstract List<GoogleTaskList> getAllLists();
@Insert(onConflict = OnConflictStrategy.REPLACE)
long insertOrReplace(GoogleTaskList googleTaskList);
public abstract long insertOrReplace(GoogleTaskList googleTaskList);
@Insert
void insert(GoogleTaskList googleTaskList);
public abstract void insert(GoogleTaskList googleTaskList);
@Insert
void insert(GoogleTaskAccount googleTaskAccount);
public abstract void insert(GoogleTaskAccount googleTaskAccount);
@Update
void update(GoogleTaskList googleTaskList);
public abstract void update(GoogleTaskList googleTaskList);
@Update
void update(GoogleTaskAccount account);
@Delete
void delete(GoogleTaskList list);
@Delete
void delete(GoogleTaskAccount account);
public abstract void update(GoogleTaskAccount account);
}

@ -28,7 +28,4 @@ public interface LocationDao {
@Insert
void insert(Location location);
@Query("DELETE FROM locations WHERE task = :taskId")
void deleteByTaskId(long taskId);
}

@ -41,9 +41,6 @@ public interface TagDao {
@Query("SELECT * FROM tags WHERE task = :taskId AND tag_uid = :tagUid")
Tag getTagByTaskAndTagUid(long taskId, String tagUid);
@Query("DELETE FROM tags WHERE task = :taskId")
void deleteByTaskId(long taskId);
@Query("DELETE FROM tags WHERE _id = :id")
void deleteById(long id);
}

@ -188,7 +188,7 @@ public class FilterCriteriaProvider {
}
private CustomFilterCriterion getGtasksFilterCriteria() {
List<GoogleTaskList> lists = googleTaskListDao.getAllActiveLists();
List<GoogleTaskList> lists = googleTaskListDao.getAllLists();
String[] listNames = new String[lists.size()];
String[] listIds = new String[lists.size()];

@ -26,6 +26,7 @@ import com.todoroo.astrid.gtasks.api.HttpNotFoundException;
import com.todoroo.astrid.gtasks.sync.GtasksSyncService;
import com.todoroo.astrid.gtasks.sync.GtasksTaskContainer;
import com.todoroo.astrid.service.TaskCreator;
import com.todoroo.astrid.service.TaskDeleter;
import com.todoroo.astrid.utility.Constants;
import java.io.IOException;
import java.util.ArrayList;
@ -34,6 +35,7 @@ import javax.inject.Inject;
import org.tasks.LocalBroadcastManager;
import org.tasks.R;
import org.tasks.analytics.Tracker;
import org.tasks.billing.Inventory;
import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskAccount;
import org.tasks.data.GoogleTaskDao;
@ -67,6 +69,8 @@ public class GoogleTaskSynchronizer {
private final PermissionChecker permissionChecker;
private final GoogleAccountManager googleAccountManager;
private final LocalBroadcastManager localBroadcastManager;
private final Inventory inventory;
private final TaskDeleter taskDeleter;
@Inject
public GoogleTaskSynchronizer(
@ -85,7 +89,9 @@ public class GoogleTaskSynchronizer {
PlayServices playServices,
PermissionChecker permissionChecker,
GoogleAccountManager googleAccountManager,
LocalBroadcastManager localBroadcastManager) {
LocalBroadcastManager localBroadcastManager,
Inventory inventory,
TaskDeleter taskDeleter) {
this.context = context;
this.googleTaskListDao = googleTaskListDao;
this.gtasksSyncService = gtasksSyncService;
@ -102,6 +108,8 @@ public class GoogleTaskSynchronizer {
this.permissionChecker = permissionChecker;
this.googleAccountManager = googleAccountManager;
this.localBroadcastManager = localBroadcastManager;
this.inventory = inventory;
this.taskDeleter = taskDeleter;
}
public static void mergeDates(long remoteDueDate, Task local) {
@ -120,11 +128,16 @@ public class GoogleTaskSynchronizer {
}
public void sync() {
for (GoogleTaskAccount account : googleTaskListDao.getAccounts()) {
List<GoogleTaskAccount> accounts = googleTaskListDao.getAccounts();
for (int i = 0 ; i < accounts.size() ; i++) {
GoogleTaskAccount account = accounts.get(i);
Timber.d("%s: start sync", account);
try {
synchronize(account);
account.setError("");
if (i == 0 || inventory.hasPro()) {
synchronize(account);
} else {
account.setError(context.getString(R.string.requires_pro_subscription));
}
} catch (UserRecoverableAuthIOException e) {
Timber.e(e);
sendNotification(context, e.getIntent());
@ -196,6 +209,7 @@ public class GoogleTaskSynchronizer {
for (final GoogleTaskList list : gtasksListService.getListsToUpdate(gtaskLists)) {
fetchAndApplyRemoteChanges(gtasksInvoker, list);
}
account.setError("");
}
private void pushLocalChanges(GtasksInvoker gtasksInvoker) throws UserRecoverableAuthIOException {
@ -345,6 +359,14 @@ public class GoogleTaskSynchronizer {
} else if (googleTask.getTask() > 0) {
task = taskDao.fetch(googleTask.getTask());
}
Boolean isDeleted = gtask.getDeleted();
Boolean isHidden = gtask.getHidden();
if ((isDeleted != null && isDeleted) || (isHidden != null && isHidden)) {
if (task != null) {
taskDeleter.delete(task);
}
continue;
}
if (task == null) {
task = taskCreator.createWithValues(null, "");
}

@ -16,6 +16,7 @@ import org.tasks.ErrorReportingSingleThreadExecutor;
import org.tasks.analytics.Tracker;
import org.tasks.data.AlarmDao;
import org.tasks.data.CaldavDao;
import org.tasks.data.DeletionDao;
import org.tasks.data.FilterDao;
import org.tasks.data.GoogleTaskDao;
import org.tasks.data.GoogleTaskListDao;
@ -149,6 +150,12 @@ public class ApplicationModule {
return taskDao;
}
@Provides
@ApplicationScope
public DeletionDao getDeletionDao(Database database) {
return database.getDeletionDao();
}
@Provides
@ApplicationScope
public JobManager getJobManager() {

@ -3,6 +3,7 @@ package org.tasks.injection;
import dagger.Subcomponent;
import org.tasks.jobs.AfterSaveIntentService;
import org.tasks.jobs.BackupJob;
import org.tasks.jobs.CleanupJob;
import org.tasks.jobs.NotificationJob;
import org.tasks.jobs.RefreshJob;
import org.tasks.jobs.SyncJob;
@ -23,4 +24,6 @@ public interface JobComponent {
void inject(BackupJob backupJob);
void inject(RefreshJob refreshJob);
void inject(CleanupJob cleanupJob);
}

@ -0,0 +1,50 @@
package org.tasks.jobs;
import android.support.annotation.NonNull;
import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.todoroo.astrid.alarms.AlarmService;
import com.todoroo.astrid.reminders.ReminderService;
import com.todoroo.astrid.timers.TimerPlugin;
import javax.inject.Inject;
import org.tasks.injection.InjectingJob;
import org.tasks.injection.JobComponent;
import org.tasks.location.GeofenceService;
import org.tasks.notifications.NotificationManager;
import timber.log.Timber;
public class CleanupJob extends InjectingJob {
static final String EXTRA_TASK_IDS = "extra_task_ids";
@Inject NotificationManager notificationManager;
@Inject GeofenceService geofenceService;
@Inject TimerPlugin timerPlugin;
@Inject ReminderService reminderService;
@Inject AlarmService alarmService;
@NonNull
@Override
protected Result onRunJob(@NonNull Params params) {
super.onRunJob(params);
PersistableBundleCompat extras = params.getExtras();
long[] tasks = extras.getLongArray(EXTRA_TASK_IDS);
if (tasks == null) {
Timber.e("No task ids provided");
return Result.FAILURE;
}
for (long task : tasks) {
alarmService.cancelAlarms(task);
reminderService.cancelReminder(task);
notificationManager.cancel(task);
geofenceService.cancelGeofences(task);
}
timerPlugin.updateNotifications();
return Result.SUCCESS;
}
@Override
protected void inject(JobComponent component) {
component.inject(this);
}
}

@ -16,6 +16,7 @@ public class JobCreator implements com.evernote.android.job.JobCreator {
static final String TAG_NOTIFICATION = "tag_notification";
static final String TAG_BACKGROUND_SYNC = "tag_background_sync";
static final String TAG_SYNC = "tag_sync";
static final String TAG_CLEANUP = "tag_cleanup";
@Inject
public JobCreator() {}
@ -34,6 +35,8 @@ public class JobCreator implements com.evernote.android.job.JobCreator {
case TAG_MIDNIGHT_REFRESH:
case TAG_REFRESH:
return new RefreshJob();
case TAG_CLEANUP:
return new CleanupJob();
default:
Timber.e("Unhandled tag: %s", tag);
return null;

@ -1,5 +1,6 @@
package org.tasks.jobs;
import static org.tasks.jobs.CleanupJob.EXTRA_TASK_IDS;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
import static org.tasks.time.DateTimeUtils.printTimestamp;
@ -7,6 +8,9 @@ import com.evernote.android.job.DailyJob;
import com.evernote.android.job.JobRequest;
import com.evernote.android.job.JobRequest.Builder;
import com.evernote.android.job.JobRequest.NetworkType;
import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.google.common.primitives.Longs;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.inject.Inject;
@ -45,6 +49,12 @@ public class JobManager {
this.googleTaskListDao = googleTaskListDao;
}
public void cleanup(List<Long> ids) {
PersistableBundleCompat extras = new PersistableBundleCompat();
extras.putLongArray(EXTRA_TASK_IDS, Longs.toArray(ids));
new JobRequest.Builder(JobCreator.TAG_CLEANUP).setExtras(extras).startNow().build().schedule();
}
public void scheduleNotification(long time) {
Timber.d("schedule notification: %s", printTimestamp(time));
new JobRequest.Builder(JobCreator.TAG_NOTIFICATION)

@ -16,7 +16,6 @@ import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity;
import com.todoroo.astrid.service.TaskDeleter;
import java.util.List;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.analytics.Tracker;
@ -28,7 +27,6 @@ import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavDao;
import org.tasks.data.GoogleTaskAccount;
import org.tasks.data.GoogleTaskDao;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.GoogleTaskListDao;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.gtasks.GoogleAccountManager;
@ -146,11 +144,7 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
.setPositiveButton(
R.string.logout,
(dialog, which) -> {
for (GoogleTaskList list : googleTaskListDao.getActiveLists(name)) {
taskDeleter.markDeleted(googleTaskDao.getActiveTasks(list.getRemoteId()));
googleTaskListDao.delete(list);
}
googleTaskListDao.delete(account);
taskDeleter.delete(account);
restart();
})
.setNegativeButton(android.R.string.cancel, null)
@ -273,6 +267,11 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
}
}
@Override
protected String getHelpUrl() {
return "http://tasks.org/subscribe";
}
@Override
public void inject(ActivityComponent component) {
component.inject(this);

Loading…
Cancel
Save