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 8 years ago
parent f0fd3fa944
commit fd79338c26

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

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

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

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

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

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

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

@ -292,7 +292,6 @@ public final class TaskEditFragment extends InjectingFragment
.setPositiveButton( .setPositiveButton(
android.R.string.ok, android.R.string.ok,
(dialog, which) -> { (dialog, which) -> {
timerPlugin.stopTimer(model);
taskDeleter.markDeleted(model); taskDeleter.markDeleted(model);
callback.taskEditFinished(); 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 */ /** Schedules alarms for a single task */
private void scheduleAlarms(long taskId) { private void scheduleAlarms(long taskId) {
for (Alarm alarm : getActiveAlarmsForTask(taskId)) { for (Alarm alarm : getActiveAlarmsForTask(taskId)) {

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

@ -8,7 +8,9 @@ package com.todoroo.astrid.dao;
import static com.todoroo.andlib.utility.DateUtilities.now; import static com.todoroo.andlib.utility.DateUtilities.now;
import android.arch.persistence.room.Dao; import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert; import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Transaction;
import android.arch.persistence.room.Update; import android.arch.persistence.room.Update;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
@ -23,6 +25,8 @@ import com.todoroo.astrid.helper.UUIDHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.tasks.BuildConfig; import org.tasks.BuildConfig;
import org.tasks.data.GoogleTaskAccount;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.LimitOffsetDataSource; import org.tasks.data.LimitOffsetDataSource;
import org.tasks.jobs.AfterSaveIntentService; import org.tasks.jobs.AfterSaveIntentService;
import timber.log.Timber; import timber.log.Timber;
@ -58,7 +62,7 @@ public abstract class TaskDao {
@android.arch.persistence.room.Query("SELECT * FROM tasks WHERE _id IN (:taskIds)") @android.arch.persistence.room.Query("SELECT * FROM tasks WHERE _id IN (:taskIds)")
public abstract List<Task> fetch(List<Long> 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(); public abstract int activeTimers();
@android.arch.persistence.room.Query( @android.arch.persistence.room.Query(
@ -130,16 +134,6 @@ public abstract class TaskDao {
+ "WHERE completed > 0 AND calendarUri NOT NULL AND calendarUri != ''") + "WHERE completed > 0 AND calendarUri NOT NULL AND calendarUri != ''")
public abstract int clearCompletedCalendarEvents(); 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 * Saves the given task to the database.getDatabase(). Task must already exist. Returns true on
* success. * 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.newArrayList;
import static com.google.common.collect.Lists.transform; import static com.google.common.collect.Lists.transform;
import static java.util.Collections.emptyList;
import android.support.v4.util.Pair; import android.support.v4.util.Pair;
import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.GtasksFilter; import com.todoroo.astrid.api.GtasksFilter;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.data.GoogleTaskAccount; import org.tasks.data.GoogleTaskAccount;
@ -41,7 +39,7 @@ public class GtasksFilterExposer {
public List<Pair<GoogleTaskAccount, List<Filter>>> getFilters() { public List<Pair<GoogleTaskAccount, List<Filter>>> getFilters() {
List<Pair<GoogleTaskAccount, List<Filter>>> listFilters = newArrayList(); List<Pair<GoogleTaskAccount, List<Filter>>> listFilters = newArrayList();
for (GoogleTaskAccount account : googleTaskListDao.getAccounts()) { 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))); listFilters.add(new Pair<>(account, transform(lists, GtasksFilter::new)));
} }
return listFilters; return listFilters;

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

@ -34,14 +34,6 @@ public class GtasksTaskContainer {
task.setCreationDate(DateUtilities.now()); task.setCreationDate(DateUtilities.now());
task.setCompletionDate( task.setCompletionDate(
GtasksApiUtilities.gtasksCompletedTimeToUnixTime(remoteTask.getCompleted())); 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()); long dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.getDue());
mergeDates(Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate), task); 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) { public void scheduleAlarm(Task task) {
if (task == null || !task.isSaved()) { if (task == null || !task.isSaved()) {
return; 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 // 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. // will schedule the next one after it, and so on and so forth.
jobs.cancelReminder(taskId); cancelReminder(taskId);
if (task.isCompleted() || task.isDeleted()) { if (task.isCompleted() || task.isDeleted()) {
return; 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.isVisible;
import static com.todoroo.astrid.dao.TaskDao.TaskCriteria.notCompleted; 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.api.Filter;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.calendars.CalendarEventProvider; import org.tasks.LocalBroadcastManager;
import org.tasks.data.AlarmDao; import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavDao; import org.tasks.data.CaldavCalendar;
import org.tasks.data.GoogleTaskDao; import org.tasks.data.DeletionDao;
import org.tasks.data.LocationDao; import org.tasks.data.GoogleTaskAccount;
import org.tasks.data.TagDao; import org.tasks.data.GoogleTaskList;
import org.tasks.jobs.JobManager;
public class TaskDeleter { public class TaskDeleter {
private final JobManager jobManager;
private final TaskDao taskDao; private final TaskDao taskDao;
private final CalendarEventProvider calendarEventProvider; private final LocalBroadcastManager localBroadcastManager;
private final AlarmDao alarmDao; private final DeletionDao deletionDao;
private final LocationDao locationDao;
private final TagDao tagDao;
private final GoogleTaskDao googleTaskDao;
private final CaldavDao caldavDao;
@Inject @Inject
public TaskDeleter( public TaskDeleter(DeletionDao deletionDao, JobManager jobManager, TaskDao taskDao, LocalBroadcastManager localBroadcastManager) {
TaskDao taskDao, this.deletionDao = deletionDao;
CalendarEventProvider calendarEventProvider, this.jobManager = jobManager;
AlarmDao alarmDao,
LocationDao locationDao,
TagDao tagDao,
GoogleTaskDao googleTaskDao,
CaldavDao caldavDao) {
this.taskDao = taskDao; this.taskDao = taskDao;
this.calendarEventProvider = calendarEventProvider; this.localBroadcastManager = localBroadcastManager;
this.alarmDao = alarmDao;
this.locationDao = locationDao;
this.tagDao = tagDao;
this.googleTaskDao = googleTaskDao;
this.caldavDao = caldavDao;
} }
public int purgeDeleted() { public int purgeDeleted() {
List<Task> deleted = taskDao.getDeleted(); List<Long> deleted = deletionDao.getDeleted();
for (Task task : deleted) { deletionDao.delete(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);
}
return deleted.size(); return deleted.size();
} }
public void markDeleted(Task item) { public void markDeleted(Task item) {
if (!item.isSaved()) { markDeleted(ImmutableList.of(item.getId()));
return;
}
item.setDeletionDate(now());
taskDao.save(item);
} }
public List<Task> markDeleted(List<Long> taskIds) { public List<Task> markDeleted(List<Long> taskIds) {
List<Task> tasks = taskDao.fetch(taskIds); List<Task> tasks = taskDao.fetch(taskIds);
for (Task task : tasks) { deletionDao.markDeleted(now(), taskIds);
markDeleted(task); jobManager.cleanup(taskIds);
} jobManager.syncNow();
localBroadcastManager.broadcastRefresh();
return tasks; 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) { public int clearCompleted(Filter filter) {
List<Task> completed = new ArrayList<>(); List<Long> completed = new ArrayList<>();
String query = String query =
filter filter
.getSqlQuery() .getSqlQuery()
@ -87,12 +73,38 @@ public class TaskDeleter {
.replace(notCompleted().toString(), all.toString()); .replace(notCompleted().toString(), all.toString());
for (Task task : taskDao.fetchFiltered(query)) { for (Task task : taskDao.fetchFiltered(query)) {
if (task.isCompleted()) { if (task.isCompleted()) {
completed.add(task); completed.add(task.getId());
} }
} }
for (Task task : completed) { markDeleted(completed);
markDeleted(task);
}
return completed.size(); 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.setCreationDate(now());
clone.setModificationDate(now()); clone.setModificationDate(now());
clone.setCompletionDate(0L); clone.setCompletionDate(0L);
clone.setDeletionDate(0L);
clone.setCalendarUri(""); clone.setCalendarUri("");
clone.setUuid(UUIDHelper.newUUID()); clone.setUuid(UUIDHelper.newUUID());

@ -10,6 +10,7 @@ import static java.util.Collections.emptyList;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.QueryTemplate; import com.todoroo.andlib.sql.QueryTemplate;
import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
@ -39,7 +40,9 @@ public final class TimerFilterExposer {
Resources r = context.getResources(); Resources r = context.getResources();
Filter filter = Filter filter =
new 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; filter.icon = R.drawable.ic_timer_24dp;
return filter; return filter;
} }

@ -80,7 +80,7 @@ public class TimerPlugin {
updateNotifications(); updateNotifications();
} }
private void updateNotifications() { public void updateNotifications() {
int count = taskDao.activeTimers(); int count = taskDao.activeTimers();
if (count == 0) { if (count == 0) {
notificationManager.cancel(Constants.NOTIFICATION_TIMER); 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.activity.TaskListActivity;
import com.todoroo.astrid.api.GtasksFilter; import com.todoroo.astrid.api.GtasksFilter;
import com.todoroo.astrid.gtasks.GtasksListService; import com.todoroo.astrid.gtasks.GtasksListService;
import com.todoroo.astrid.service.TaskDeleter;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
@ -63,6 +64,7 @@ public class GoogleTaskListSettingsActivity extends ThemedInjectingAppCompatActi
@Inject Tracker tracker; @Inject Tracker tracker;
@Inject ThemeCache themeCache; @Inject ThemeCache themeCache;
@Inject ThemeColor themeColor; @Inject ThemeColor themeColor;
@Inject TaskDeleter taskDeleter;
@BindView(R.id.name) @BindView(R.id.name)
TextInputEditText name; TextInputEditText name;
@ -273,7 +275,7 @@ public class GoogleTaskListSettingsActivity extends ThemedInjectingAppCompatActi
@Override @Override
public void onListDeleted() { public void onListDeleted() {
tracker.reportEvent(Tracking.Events.GTASK_DELETE_LIST); tracker.reportEvent(Tracking.Events.GTASK_DELETE_LIST);
gtasksListService.deleteList(gtasksList); taskDeleter.delete(gtasksList);
setResult(RESULT_OK, new Intent(ACTION_DELETED)); setResult(RESULT_OK, new Intent(ACTION_DELETED));
finish(); finish();
} }

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

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

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

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

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

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

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

@ -25,18 +25,12 @@ public interface CaldavDao {
@Update @Update
void update(CaldavAccount caldavAccount); void update(CaldavAccount caldavAccount);
@Delete
void delete(CaldavAccount caldavAccount);
@Insert @Insert
long insert(CaldavCalendar caldavCalendar); long insert(CaldavCalendar caldavCalendar);
@Update @Update
void update(CaldavCalendar caldavCalendar); void update(CaldavCalendar caldavCalendar);
@Delete
void delete(CaldavCalendar caldavCalendar);
@Insert @Insert
long insert(CaldavTask caldavTask); long insert(CaldavTask caldavTask);
@ -55,19 +49,13 @@ public interface CaldavDao {
@Query("SELECT * FROM caldav_tasks WHERE calendar = :calendar AND object = :object LIMIT 1") @Query("SELECT * FROM caldav_tasks WHERE calendar = :calendar AND object = :object LIMIT 1")
CaldavTask getTask(String calendar, String object); 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") @Query("SELECT * FROM caldav_tasks WHERE task = :taskId")
List<CaldavTask> getTasks(long 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") @Query("SELECT * FROM caldav_calendar")
List<CaldavCalendar> getCalendars(); 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); List<CaldavCalendar> getCalendarsByAccount(String account);
@Query("SELECT * FROM caldav_calendar WHERE uuid = :uuid LIMIT 1") @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") "SELECT * FROM caldav_calendar WHERE account = :account AND name = :name COLLATE NOCASE LIMIT 1")
CaldavCalendar getCalendar(String account, String name); 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") @Query("SELECT object FROM caldav_tasks WHERE calendar = :calendar")
List<String> getObjects(String calendar); List<String> getObjects(String calendar);
@Query("SELECT task FROM caldav_tasks WHERE calendar = :calendar AND object IN (:objects)") @Query("SELECT task FROM caldav_tasks WHERE calendar = :calendar AND object IN (:objects)")
List<Long> getTasks(String calendar, List<String> 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)") @Query("SELECT * FROM caldav_calendar WHERE account = :account AND url NOT IN (:urls)")
List<CaldavCalendar> findDeletedCalendars(String account, List<String> 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 @Dao
public interface GoogleTaskDao { public interface GoogleTaskDao {
@Query("DELETE FROM google_tasks WHERE list_id = :remoteId")
void deleteList(String remoteId);
@Insert @Insert
void insert(GoogleTask task); 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") "SELECT * FROM google_tasks WHERE list_id = :listId AND `order` < :startAtOrder ORDER BY `order` DESC")
List<GoogleTask> getTasksFromReverse(String listId, long startAtOrder); List<GoogleTask> getTasksFromReverse(String listId, long startAtOrder);
@Query("DELETE FROM google_tasks WHERE task = :taskId")
void deleteByTaskId(long taskId);
@Delete @Delete
void delete(GoogleTask deleted); void delete(GoogleTask deleted);

@ -43,7 +43,8 @@ public class GoogleTaskList implements Parcelable {
private long lastSync; private long lastSync;
@ColumnInfo(name = "deleted") @ColumnInfo(name = "deleted")
private long deleted; @Deprecated
private long deleted = 0;
@ColumnInfo(name = "color") @ColumnInfo(name = "color")
private Integer 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.Insert;
import android.arch.persistence.room.OnConflictStrategy; import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query; import android.arch.persistence.room.Query;
import android.arch.persistence.room.Transaction;
import android.arch.persistence.room.Update; import android.arch.persistence.room.Update;
import java.util.List; import java.util.List;
@Dao @Dao
public interface GoogleTaskListDao { public abstract class GoogleTaskListDao {
@Query("SELECT * FROM google_task_accounts") @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") @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") @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") @Query("SELECT * FROM google_task_lists WHERE account = :account ORDER BY title ASC")
List<GoogleTaskList> getActiveLists(String account); public abstract List<GoogleTaskList> getLists(String account);
@Query("SELECT * FROM google_task_lists WHERE remote_id = :remoteId LIMIT 1") @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") @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") @Query("SELECT * FROM google_task_lists")
List<GoogleTaskList> getAll(); public abstract List<GoogleTaskList> getAllLists();
@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);
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
long insertOrReplace(GoogleTaskList googleTaskList); public abstract long insertOrReplace(GoogleTaskList googleTaskList);
@Insert @Insert
void insert(GoogleTaskList googleTaskList); public abstract void insert(GoogleTaskList googleTaskList);
@Insert @Insert
void insert(GoogleTaskAccount googleTaskAccount); public abstract void insert(GoogleTaskAccount googleTaskAccount);
@Update @Update
void update(GoogleTaskList googleTaskList); public abstract void update(GoogleTaskList googleTaskList);
@Update @Update
void update(GoogleTaskAccount account); public abstract void update(GoogleTaskAccount account);
@Delete
void delete(GoogleTaskList list);
@Delete
void delete(GoogleTaskAccount account);
} }

@ -28,7 +28,4 @@ public interface LocationDao {
@Insert @Insert
void insert(Location location); 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") @Query("SELECT * FROM tags WHERE task = :taskId AND tag_uid = :tagUid")
Tag getTagByTaskAndTagUid(long taskId, String 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") @Query("DELETE FROM tags WHERE _id = :id")
void deleteById(long id); void deleteById(long id);
} }

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

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

@ -3,6 +3,7 @@ package org.tasks.injection;
import dagger.Subcomponent; import dagger.Subcomponent;
import org.tasks.jobs.AfterSaveIntentService; import org.tasks.jobs.AfterSaveIntentService;
import org.tasks.jobs.BackupJob; import org.tasks.jobs.BackupJob;
import org.tasks.jobs.CleanupJob;
import org.tasks.jobs.NotificationJob; import org.tasks.jobs.NotificationJob;
import org.tasks.jobs.RefreshJob; import org.tasks.jobs.RefreshJob;
import org.tasks.jobs.SyncJob; import org.tasks.jobs.SyncJob;
@ -23,4 +24,6 @@ public interface JobComponent {
void inject(BackupJob backupJob); void inject(BackupJob backupJob);
void inject(RefreshJob refreshJob); 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_NOTIFICATION = "tag_notification";
static final String TAG_BACKGROUND_SYNC = "tag_background_sync"; static final String TAG_BACKGROUND_SYNC = "tag_background_sync";
static final String TAG_SYNC = "tag_sync"; static final String TAG_SYNC = "tag_sync";
static final String TAG_CLEANUP = "tag_cleanup";
@Inject @Inject
public JobCreator() {} public JobCreator() {}
@ -34,6 +35,8 @@ public class JobCreator implements com.evernote.android.job.JobCreator {
case TAG_MIDNIGHT_REFRESH: case TAG_MIDNIGHT_REFRESH:
case TAG_REFRESH: case TAG_REFRESH:
return new RefreshJob(); return new RefreshJob();
case TAG_CLEANUP:
return new CleanupJob();
default: default:
Timber.e("Unhandled tag: %s", tag); Timber.e("Unhandled tag: %s", tag);
return null; return null;

@ -1,5 +1,6 @@
package org.tasks.jobs; 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.currentTimeMillis;
import static org.tasks.time.DateTimeUtils.printTimestamp; 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;
import com.evernote.android.job.JobRequest.Builder; import com.evernote.android.job.JobRequest.Builder;
import com.evernote.android.job.JobRequest.NetworkType; 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 java.util.concurrent.TimeUnit;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
@ -45,6 +49,12 @@ public class JobManager {
this.googleTaskListDao = googleTaskListDao; 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) { public void scheduleNotification(long time) {
Timber.d("schedule notification: %s", printTimestamp(time)); Timber.d("schedule notification: %s", printTimestamp(time));
new JobRequest.Builder(JobCreator.TAG_NOTIFICATION) new JobRequest.Builder(JobCreator.TAG_NOTIFICATION)

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

Loading…
Cancel
Save