private void synchronize(ChangeRecord record) throws IOException {
    try {
      switch (record.getOperation()) {
        case LOCAL_INSERT:
          {
            // Get remote id for the parent folder
            File local = DriveUtils.absolutePath(record.getLocalFile());
            String remoteParent =
                stub.storage().localToRemote().get(DriveUtils.relativePath(local.getParentFile()));
            if (!StringUtils.isEmpty(remoteParent)
                && !stub.storage().localToRemote().containsKey(record.getLocalFile())) {
              // Ignore insert request that doesn't have a parent or
              // already have a mapping.
              // The insert operation will be accomplished by the topmost
              // folder
              String remoteId = stub.transmit().upload(remoteParent, local);
              record.setRemoteFileId(remoteId);
            }
            break;
          }
        case LOCAL_DELETE:
          {
            if (!StringUtils.isEmpty(record.getRemoteFileId())) {
              stub.transmit().delete(record.getRemoteFileId());
            } else {
              logger.warn(
                  "Local deletion has no remote reference, make sure the storage is correct.");
            }
            break;
          }
        case LOCAL_CHANGE:
          {
            if (!StringUtils.isEmpty(record.getRemoteFileId())) {
              File local = DriveUtils.absolutePath(record.getLocalFile());
              stub.transmit().update(record.getRemoteFileId(), local);
            } else {
              logger.warn(
                  "Local change has no remote reference, make sure the storage is correct.");
              logger.warn("Trying to insert the new record");
              // Modify it to a local insert
              record.setOperation(Operation.LOCAL_INSERT);
              synchronize(record);
            }
            break;
          }
        case LOCAL_RENAME:
          {
            if (!StringUtils.isEmpty(record.getRemoteFileId())) {
              stub.transmit().rename(record.getRemoteFileId(), (String) record.getContext()[0]);
            } else {
              logger.warn(
                  "Local rename has no remote reference, make sure the storage is correct.");
              logger.warn("Trying to insert the new record");
              // Modify it to be a local insert
              record.setOperation(Operation.LOCAL_INSERT);
              synchronize(record);
            }
            break;
          }
        case REMOTE_INSERT:
          {
            if (stub.storage().remoteToLocal().containsKey(record.getRemoteFileId())) {
              // This file/folder already had been created
              break;
            }
            com.google.api.services.drive.model.File file = record.getContext(0);

            // Depth search of parent that has a local root
            List<ParentReference> path = new ArrayList<ParentReference>();
            File local = pathToLocal(record.getRemoteFileId(), path);
            if (null == local) {
              // Check whether this file is trashed
              if (file.getLabels().getTrashed()) {
                return;
              }
              if (file.getShared()) {
                return;
              } else {
                throw new IllegalArgumentException("Non-trash file has no known parent");
              }
            }
            File parent = local;
            for (ParentReference node : path) {
              stub.transmit().download(node.getId(), parent);
              parent = DriveUtils.absolutePath(stub.storage().remoteToLocal().get(node.getId()));
            }
            stub.transmit().download(record.getRemoteFileId(), parent);
            record.setLocalFile(stub.storage().remoteToLocal().get(record.getRemoteFileId()));
            if (StringUtils.isEmpty(record.getLocalFile())) {
              break;
            }
            break;
          }
        case REMOTE_DELETE:
          {
            if (StringUtils.isEmpty(record.getLocalFile())) {
              // No local file, remote file should be a trashed one.
              if (logger.isDebugEnabled()) {
                logger.debug(
                    "No corresponding local file for deletion. Remote file may be trashed");
              }
              break;
            }
            File localFile = DriveUtils.absolutePath(record.getLocalFile());
            String localParent = DriveUtils.relativePath(localFile.getParentFile());
            // Preserve the original context
            record.setContext(new Object[] {record.getContext(0), localParent});
            deleteLocalFile(localFile);
            if (localFile.exists()) {
              if (logger.isDebugEnabled()) {
                logger.debug(
                    MessageFormat.format(
                        "Failed to delete file {0}. File may have been deleted.",
                        record.getLocalFile()));
              }
            }
            stub.storage().localToRemote().remove(record.getLocalFile());
            stub.storage().remoteToLocal().remove(record.getRemoteFileId());
            break;
          }
        case REMOTE_CHANGE:
          {
            File local = DriveUtils.absolutePath(record.getLocalFile());
            File localParent = local.getParentFile();
            com.google.api.services.drive.model.File remote = record.getContext(0);

            if (!remote.getTitle().equals(local.getName())) {
              local.delete();
              stub.storage().localToRemote().remove(record.getLocalFile());
              stub.storage().remoteToLocal().remove(record.getRemoteFileId());
            }
            stub.transmit().download(record.getRemoteFileId(), localParent);
            break;
          }
        case REMOTE_RENAME:
          {
            File local = DriveUtils.absolutePath(record.getLocalFile());
            com.google.api.services.drive.model.File remote = record.getContext(0);
            String remoteName = remote.getTitle();
            File newName =
                new File(local.getParentFile().getAbsolutePath() + File.separator + remoteName);
            String relNewName = DriveUtils.relativePath(newName);
            record.setContext(new Object[] {record.getContext()[0], relNewName});
            local.renameTo(newName);
            stub.storage()
                .remoteToLocal()
                .put(record.getRemoteFileId(), DriveUtils.relativePath(newName));
            stub.storage().localToRemote().remove(DriveUtils.relativePath(local));
            stub.storage()
                .localToRemote()
                .put(DriveUtils.relativePath(newName), record.getRemoteFileId());
            break;
          }
      }
    } catch (Exception e) {
      logger.warn(MessageFormat.format("Exception on Change {0}. Retry later", record), e);
      FailedRecord fr = new FailedRecord(record);
      if (e instanceof GoogleJsonResponseException) {
        GoogleJsonResponseException gjre = (GoogleJsonResponseException) e;
        fr.setError(String.valueOf(gjre.getDetails().getCode()));
      } else {
        fr.setError(e.getMessage());
      }
      stub.storage().failedLog().add(fr);
    }
  }
 private FailedRecord correct(FailedRecord fr) {
   if (fr.getOperation() == Operation.LOCAL_CHANGE
       || fr.getOperation() == Operation.LOCAL_RENAME) {
     if (StringUtils.isEmpty(fr.getRemoteFileId()) || StringUtils.isEmpty(fr.getError())) {
       fr.setOperation(Operation.LOCAL_INSERT);
     } else if (!StringUtils.isEmpty(fr.getError())
         && fr.getError().startsWith("Local file doesn't exist")) {
       return null;
     }
   }
   if (fr.getOperation() == Operation.LOCAL_INSERT) {
     if (!StringUtils.isEmpty(fr.getError())
         && fr.getError().startsWith("Local file doesn't exist")) {
       return null;
     }
   }
   if (fr.getOperation() == Operation.LOCAL_DELETE
       && (StringUtils.isEmpty(fr.getRemoteFileId()) || "404".equals(fr.getError()))) {
     return null;
   }
   if ("404".equals(fr.getError())
       && (fr.getOperation() == Operation.LOCAL_CHANGE
           || fr.getOperation() == Operation.LOCAL_DELETE
           || fr.getOperation() == Operation.LOCAL_RENAME)) {
     // No such file
     fr.setOperation(Operation.LOCAL_INSERT);
     return fr;
   }
   if ("404".equals(fr.getError())
       && (fr.getOperation() == Operation.REMOTE_INSERT
           || fr.getOperation() == Operation.REMOTE_CHANGE
           || fr.getOperation() == Operation.REMOTE_RENAME
           || fr.getOperation() == Operation.REMOTE_DELETE)) {
     return null;
   }
   return fr;
 }