private Payload doInBackgroundRegister(Payload data) {
   String username = (String) data.data[0];
   String password = (String) data.data[1];
   BasicHttpSyncer server = new RemoteServer(this, null);
   HttpResponse ret = server.register(username, password);
   String hostkey = null;
   boolean valid = false;
   data.returnType = ret.getStatusLine().getStatusCode();
   String status = null;
   if (data.returnType == 200) {
     try {
       JSONObject jo = (new JSONObject(server.stream2String(ret.getEntity().getContent())));
       status = jo.getString("status");
       if (status.equals("ok")) {
         hostkey = jo.getString("hkey");
         valid = (hostkey != null) && (hostkey.length() > 0);
       }
     } catch (JSONException e) {
     } catch (IllegalStateException e) {
       throw new RuntimeException(e);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
   if (valid) {
     data.success = true;
     data.data = new String[] {username, hostkey};
   } else {
     data.success = false;
     if (status != null) {
       data.data = new String[] {status};
     }
   }
   return data;
 }
 private Payload doInBackgroundLogin(Payload data) {
   String username = (String) data.data[0];
   String password = (String) data.data[1];
   BasicHttpSyncer server = new RemoteServer(this, null);
   HttpResponse ret = server.hostKey(username, password);
   String hostkey = null;
   boolean valid = false;
   if (ret != null) {
     data.returnType = ret.getStatusLine().getStatusCode();
     Log.i(
         AnkiDroidApp.TAG,
         "doInBackgroundLogin - response from server: "
             + data.returnType
             + " ("
             + ret.getStatusLine().getReasonPhrase()
             + ")");
     if (data.returnType == 200) {
       try {
         JSONObject jo = (new JSONObject(server.stream2String(ret.getEntity().getContent())));
         hostkey = jo.getString("key");
         valid = (hostkey != null) && (hostkey.length() > 0);
       } catch (JSONException e) {
         valid = false;
       } catch (IllegalStateException e) {
         throw new RuntimeException(e);
       } catch (IOException e) {
         throw new RuntimeException(e);
       }
     }
   } else {
     Log.e(AnkiDroidApp.TAG, "doInBackgroundLogin - empty response from server");
   }
   if (valid) {
     data.success = true;
     data.data = new String[] {username, hostkey};
   } else {
     data.success = false;
   }
   return data;
 }
  /**
   * Downloads any missing media files according to the mediaURL deckvar.
   *
   * @param data
   * @return The return type contains data.resultType and an array of Integer in data.data.
   *     data.data[0] is the number of total missing media, data.data[1] is the number of downloaded
   *     ones.
   */
  private Payload doInBackgroundDownloadMissingMedia(Payload data) {
    Log.i(AnkiDroidApp.TAG, "DownloadMissingMedia");
    HashMap<String, String> missingPaths = new HashMap<String, String>();
    HashMap<String, String> missingSums = new HashMap<String, String>();

    Decks deck = (Decks) data.data[0];
    data.result = deck; // pass it to the return object so we close the deck in the deck picker
    String syncName = ""; // deck.getDeckName();

    data.success = false;
    data.data = new Object[] {0, 0, 0};
    //        if (!deck.hasKey("mediaURL")) {
    //            data.success = true;
    //            return data;
    //        }
    String urlbase = ""; // deck.getVar("mediaURL");
    if (urlbase.equals("")) {
      data.success = true;
      return data;
    }

    String mdir = ""; // deck.mediaDir(true);
    int totalMissing = 0;
    int missing = 0;
    int grabbed = 0;

    Cursor cursor = null;
    try {
      cursor =
          null; // deck.getDB().getDatabase().rawQuery("SELECT filename, originalPath FROM media",
      // null);
      String path = null;
      String f = null;
      while (cursor.moveToNext()) {
        f = cursor.getString(0);
        path = mdir + "/" + f;
        File file = new File(path);
        if (!file.exists()) {
          missingPaths.put(f, path);
          missingSums.put(f, cursor.getString(1));
          Log.i(AnkiDroidApp.TAG, "Missing file: " + f);
        }
      }
    } finally {
      if (cursor != null) {
        cursor.close();
      }
    }

    totalMissing = missingPaths.size();
    data.data[0] = new Integer(totalMissing);
    if (totalMissing == 0) {
      data.success = true;
      return data;
    }
    publishProgress(Boolean.FALSE, new Integer(totalMissing), new Integer(0), syncName);

    URL url = null;
    HttpURLConnection connection = null;
    String path = null;
    String sum = null;
    int readbytes = 0;
    byte[] buf = new byte[4096];
    for (String file : missingPaths.keySet()) {

      try {
        android.net.Uri uri = android.net.Uri.parse(Uri.encode(urlbase, ":/@%") + Uri.encode(file));
        url = new URI(uri.toString()).toURL();
        connection = (HttpURLConnection) url.openConnection();
        connection.connect();
        if (connection.getResponseCode() == 200) {
          path = missingPaths.get(file);
          InputStream is = connection.getInputStream();
          BufferedInputStream bis = new BufferedInputStream(is, 4096);
          FileOutputStream fos = new FileOutputStream(path);
          while ((readbytes = bis.read(buf, 0, 4096)) != -1) {
            fos.write(buf, 0, readbytes);
            Log.i(AnkiDroidApp.TAG, "Downloaded " + readbytes + " file: " + path);
          }
          fos.close();

          // Verify with checksum
          sum = missingSums.get(file);
          if (true) { // sum.equals("") || sum.equals(Utils.fileChecksum(path))) {
            grabbed++;
          } else {
            // Download corrupted, delete file
            Log.i(AnkiDroidApp.TAG, "Downloaded media file " + path + " failed checksum.");
            File f = new File(path);
            f.delete();
            missing++;
          }
        } else {
          Log.e(
              AnkiDroidApp.TAG,
              "Connection error ("
                  + connection.getResponseCode()
                  + ") while retrieving media file "
                  + urlbase
                  + file);
          Log.e(AnkiDroidApp.TAG, "Connection message: " + connection.getResponseMessage());
          if (missingSums.get(file).equals("")) {
            // Ignore and keep going
            missing++;
          } else {
            data.success = false;
            data.data = new Object[] {file};
            return data;
          }
        }
        connection.disconnect();
      } catch (URISyntaxException e) {
        Log.e(AnkiDroidApp.TAG, Log.getStackTraceString(e));
      } catch (MalformedURLException e) {
        Log.e(AnkiDroidApp.TAG, Log.getStackTraceString(e));
        Log.e(AnkiDroidApp.TAG, "MalformedURLException while download media file " + path);
        if (missingSums.get(file).equals("")) {
          // Ignore and keep going
          missing++;
        } else {
          data.success = false;
          data.data = new Object[] {file};
          return data;
        }
      } catch (IOException e) {
        Log.e(AnkiDroidApp.TAG, Log.getStackTraceString(e));
        Log.e(AnkiDroidApp.TAG, "IOException while download media file " + path);
        if (missingSums.get(file).equals("")) {
          // Ignore and keep going
          missing++;
        } else {
          data.success = false;
          data.data = new Object[] {file};
          return data;
        }
      } finally {
        if (connection != null) {
          connection.disconnect();
        }
      }
      publishProgress(
          Boolean.TRUE, new Integer(totalMissing), new Integer(grabbed + missing), syncName);
    }

    data.data[1] = new Integer(grabbed);
    data.data[2] = new Integer(missing);
    data.success = true;
    return data;
  }
  private Payload doInBackgroundSync(Payload data) {
    // for for doInBackgroundLoadDeckCounts if any
    DeckTask.waitToFinish();

    String hkey = (String) data.data[0];
    boolean media = (Boolean) data.data[1];
    String conflictResolution = (String) data.data[2];
    int mediaUsn = (Integer) data.data[3];

    Collection col = Collection.currentCollection();
    if (col == null) {
      data.success = false;
      data.result = new Object[] {"genericError"};
      return data;
    }
    String path = col.getPath();

    BasicHttpSyncer server = new RemoteServer(this, hkey);
    Syncer client = new Syncer(col, server);

    // run sync and check state
    boolean noChanges = false;
    if (conflictResolution == null) {
      Log.i(AnkiDroidApp.TAG, "Sync - starting sync");
      publishProgress(R.string.sync_prepare_syncing);
      Object[] ret = client.sync(this);
      mediaUsn = client.getmMediaUsn();
      if (ret == null) {
        data.success = false;
        data.result = new Object[] {"genericError"};
        return data;
      }
      String retCode = (String) ret[0];
      if (!retCode.equals("noChanges") && !retCode.equals("success")) {
        data.success = false;
        data.result = ret;
        // note mediaUSN for later
        data.data = new Object[] {mediaUsn};
        return data;
      }
      // save and note success state
      col.save();
      if (retCode.equals("noChanges")) {
        //    			publishProgress(R.string.sync_no_changes_message);
        noChanges = true;
      } else {
        //    			publishProgress(R.string.sync_database_success);
      }
    } else {
      try {
        server = new FullSyncer(col, hkey, this);
        if (conflictResolution.equals("upload")) {
          Log.i(AnkiDroidApp.TAG, "Sync - fullsync - upload collection");
          publishProgress(R.string.sync_preparing_full_sync_message);
          Object[] ret = server.upload();
          if (ret == null) {
            data.success = false;
            data.result = new Object[] {"genericError"};
            data.data = new Object[] {Collection.openCollection(path)};
            return data;
          }
          if (!((String) ret[0]).equals(BasicHttpSyncer.ANKIWEB_STATUS_OK)) {
            data.success = false;
            data.result = ret;
            data.data = new Object[] {Collection.openCollection(path)};
            return data;
          }
        } else if (conflictResolution.equals("download")) {
          Log.i(AnkiDroidApp.TAG, "Sync - fullsync - download collection");
          publishProgress(R.string.sync_downloading_message);
          Object[] ret = server.download();
          if (ret == null) {
            data.success = false;
            data.result = new Object[] {"genericError"};
            data.data = new Object[] {Collection.openCollection(path)};
            return data;
          }
          if (!((String) ret[0]).equals("success")) {
            data.success = false;
            data.result = ret;
            data.data = new Object[] {Collection.openCollection(path)};
            return data;
          }
        }
      } finally {
        publishProgress(R.string.sync_reload_message);
        col = Collection.openCollection(path);
      }
    }

    // then move on to media sync
    boolean noMediaChanges = false;
    if (media) {
      server = new RemoteMediaServer(hkey, this);
      MediaSyncer mediaClient = new MediaSyncer(col, (RemoteMediaServer) server);
      String ret = mediaClient.sync(mediaUsn, this);
      if (ret.equals("noChanges")) {
        publishProgress(R.string.sync_media_no_changes);
        noMediaChanges = true;
      } else {
        publishProgress(R.string.sync_media_success);
      }
    }
    if (noChanges && noMediaChanges) {
      data.success = false;
      data.result = new Object[] {"noChanges"};
      return data;
    } else {
      data.success = true;
      TreeSet<Object[]> decks = col.getSched().deckDueTree(Sched.DECK_INFORMATION_SIMPLE_COUNTS);
      int[] counts = new int[] {0, 0, 0};
      for (Object[] deck : decks) {
        if (((String[]) deck[0]).length == 1) {
          counts[0] += (Integer) deck[2];
          counts[1] += (Integer) deck[3];
          counts[2] += (Integer) deck[4];
        }
      }
      data.result = decks;
      data.data =
          new Object[] {conflictResolution, col, col.getSched().eta(counts), col.cardCount()};
      return data;
    }
  }