private synchronized void synchronizeListHelper(
      StoreObject list,
      GtasksInvoker invoker,
      boolean manual,
      SyncExceptionHandler errorHandler,
      SyncResultCallback callback) {
    String listId = list.getValue(GtasksList.REMOTE_ID);
    long lastSyncDate;
    if (!manual && list.containsNonNullValue(GtasksList.LAST_SYNC)) {
      lastSyncDate = list.getValue(GtasksList.LAST_SYNC);
    } else {
      lastSyncDate = 0;
    }
    boolean includeDeletedAndHidden = lastSyncDate != 0;
    try {
      Tasks taskList =
          invoker.getAllGtasksFromListId(
              listId, includeDeletedAndHidden, includeDeletedAndHidden, lastSyncDate);
      List<com.google.api.services.tasks.model.Task> tasks = taskList.getItems();
      if (tasks != null) {
        callback.incrementMax(tasks.size() * 10);
        HashSet<Long> localIds = new HashSet<Long>(tasks.size());
        for (com.google.api.services.tasks.model.Task t : tasks) {
          GtasksTaskContainer container = parseRemoteTask(t, listId);
          gtasksMetadataService.findLocalMatch(container);
          container.gtaskMetadata.setValue(
              GtasksMetadata.GTASKS_ORDER, Long.parseLong(t.getPosition()));
          container.gtaskMetadata.setValue(
              GtasksMetadata.PARENT_TASK, gtasksMetadataService.localIdForGtasksId(t.getParent()));
          container.gtaskMetadata.setValue(GtasksMetadata.LAST_SYNC, DateUtilities.now() + 1000L);
          write(container);
          localIds.add(container.task.getId());
          callback.incrementProgress(10);
        }
        list.setValue(GtasksList.LAST_SYNC, DateUtilities.now());
        storeObjectDao.persist(list);

        if (lastSyncDate == 0) {
          Long[] localIdArray = localIds.toArray(new Long[localIds.size()]);
          Criterion delete =
              Criterion.and(
                  Metadata.KEY.eq(GtasksMetadata.METADATA_KEY),
                  GtasksMetadata.LIST_ID.eq(listId),
                  Criterion.not(Metadata.TASK.in(localIdArray)));
          taskService.deleteWhere(
              Task.ID.in(Query.select(Metadata.TASK).from(Metadata.TABLE).where(delete)));
          metadataService.deleteWhere(delete);
        }

        gtasksTaskListUpdater.correctOrderAndIndentForList(listId);
      }
    } catch (GoogleTasksException e) {
      if (errorHandler != null)
        errorHandler.handleException("gtasks-sync-io", e, e.getType()); // $NON-NLS-1$
    } catch (IOException e) {
      if (errorHandler != null)
        errorHandler.handleException("gtasks-sync-io", e, e.toString()); // $NON-NLS-1$
    }
  }
  public void checkAndMigrateLegacy() throws IOException {
    if (!gtasksPreferenceService.migrationHasOccurred()) {

      // Fetch all tasks that have associated gtask metadata
      String defaultListTitle =
          gtasksListService.getListName(
              Preferences.getStringValue(GtasksPreferenceService.PREF_DEFAULT_LIST));
      String defaultListId = null;

      TodorooCursor<Task> allTasksWithGtaskData =
          taskService.query(
              Query.select(Task.PROPERTIES)
                  .where(
                      Task.ID.in(
                          Query.select(Metadata.TASK)
                              .from(Metadata.TABLE)
                              .where(Metadata.KEY.eq(GtasksMetadata.METADATA_KEY)))));

      try {
        if (allTasksWithGtaskData.getCount() > 0) {
          // Fetch all remote tasks from all remote lists (this may be an expensive operation)
          // and map their titles to their real remote ids
          HashMap<String, String> taskAndListTitlesToRemoteTaskIds = new HashMap<String, String>();

          List<TaskList> items = allLists.getItems();
          for (TaskList list : items) {
            if (list.getTitle().equals(defaultListTitle)) {
              defaultListId = list.getId();
            }

            Tasks allTasks = gtasksService.getAllGtasksFromListId(list.getId(), false, false, 0);

            List<com.google.api.services.tasks.model.Task> tasksItems = allTasks.getItems();
            if (tasksItems != null) {
              for (com.google.api.services.tasks.model.Task t : tasksItems) {
                String key = constructKeyFromTitles(t.getTitle(), list.getTitle());
                taskAndListTitlesToRemoteTaskIds.put(key, t.getId());
              }
            }
          }

          if (defaultListId == null) {
            com.google.api.services.tasks.model.TaskList defaultList =
                gtasksService.getGtaskList("@default"); // $NON-NLS-1$
            defaultListId = defaultList.getId();
          }
          Preferences.setString(GtasksPreferenceService.PREF_DEFAULT_LIST, defaultListId);

          // For each local task, check to see if its title paired with any list title has a match
          // in the map
          for (allTasksWithGtaskData.moveToFirst();
              !allTasksWithGtaskData.isAfterLast();
              allTasksWithGtaskData.moveToNext()) {
            GtasksTaskContainer container =
                gtasksMetadataService.readTaskAndMetadata(allTasksWithGtaskData);
            // memorize the original listname for the case that the task is not matched,
            // then it should at least be recreated in the correct list
            String originalListName =
                gtasksListService.getListName(
                    container.gtaskMetadata.getValue(GtasksMetadata.LIST_ID));
            String originalListId = null;

            // Search through lists to see if one of them has match
            String taskTitle = container.task.getValue(Task.TITLE);
            boolean foundMatch = false;
            items = allLists.getItems();
            for (TaskList list : items) {
              String expectedKey = constructKeyFromTitles(taskTitle, list.getTitle());

              // save the new id of the current list
              // if it matches the listname of the current task
              if (list.getTitle() != null && list.getTitle().equals(originalListName)) {
                originalListId = list.getId();
              }

              if (taskAndListTitlesToRemoteTaskIds.containsKey(expectedKey)) {
                foundMatch = true;
                String newRemoteTaskId = taskAndListTitlesToRemoteTaskIds.get(expectedKey);
                String newRemoteListId = list.getId();

                container.gtaskMetadata.setValue(GtasksMetadata.ID, newRemoteTaskId);
                container.gtaskMetadata.setValue(GtasksMetadata.LIST_ID, newRemoteListId);
                gtasksMetadataService.saveTaskAndMetadata(container);
                break;
              }
            }

            if (!foundMatch) {
              // For non-matches, make the task look newly created
              container.gtaskMetadata = GtasksMetadata.createEmptyMetadata(container.task.getId());
              container.gtaskMetadata.setValue(GtasksMetadata.ID, ""); // $NON-NLS-1$
              if (originalListId != null) {
                // set the list-id based on the original listname, saved above during for-loop
                container.gtaskMetadata.setValue(GtasksMetadata.LIST_ID, originalListId);
              } else {
                // remote list or local list was renamed, so put this unmatched task in the default
                // list
                container.gtaskMetadata.setValue(GtasksMetadata.LIST_ID, defaultListId);
              }
              gtasksMetadataService.saveTaskAndMetadata(container);
              break;
            }
          }
        }

        // migrate the list-id's afterwards, so that we can put the non-matched tasks in their
        // original lists
        // if the listnames didnt change before migration (defaultlist otherwise)
        listService.migrateListIds(allLists);

      } finally {
        allTasksWithGtaskData.close();
      }
      Preferences.setBoolean(
          GtasksPreferenceService.PREF_MIGRATION_HAS_OCCURRED, true); // Record successful migration
    }
  }