private Payload doInBackgroundUpgradeDecks(Payload data) { String path = (String) data.data[0]; File ankiDir = new File(path); if (!ankiDir.isDirectory()) { data.success = false; return data; } // step 1: gather all .anki files into a zip, without media. // we must store them as 1.anki, 2.anki and provide a map so we don't run into // encoding issues with the zip file. File[] fileList = ankiDir.listFiles(new OldAnkiDeckFilter()); JSONObject map = new JSONObject(); byte[] buf = new byte[1024]; String zipFilename = path + "/upload.zip"; String colFilename = path + "/collection.anki2"; try { ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFilename)); int n = 1; for (File f : fileList) { String tmpName = n + ".anki"; FileInputStream in = new FileInputStream(f.getAbsolutePath()); ZipEntry ze = new ZipEntry(tmpName); zos.putNextEntry(ze); int len; while ((len = in.read(buf)) > 0) { zos.write(buf, 0, len); } zos.closeEntry(); map.put(tmpName, f.getName()); n++; } ZipEntry ze = new ZipEntry("map.json"); zos.putNextEntry(ze); InputStream in = new ByteArrayInputStream(map.toString().getBytes("UTF-8")); int len; while ((len = in.read(buf)) > 0) { zos.write(buf, 0, len); } zos.closeEntry(); zos.close(); } catch (FileNotFoundException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } catch (JSONException e) { throw new RuntimeException(e); } File zipFile = new File(zipFilename); // step 1.1: if it's over 50MB compressed, it must be upgraded by the user if (zipFile.length() > 50 * 1024 * 1024) { data.success = false; return data; } // step 2: upload zip file to upgrade service and get token BasicHttpSyncer h = new BasicHttpSyncer(null, null); // note: server doesn't expect it to be gzip compressed, because the zip file is compressed publishProgress(new Object[] {R.string.upgrade_decks_upload}); try { HttpResponse resp = h.req("upgrade/upload", new FileInputStream(zipFile), 0, false); String result = h.stream2String(resp.getEntity().getContent()); String key; if (result.startsWith("ok:")) { key = result.split(":")[1]; } else { data.success = false; return data; } while (true) { result = h.stream2String(h.req("upgrade/status?key=" + key).getEntity().getContent()); if (result.equals("error")) { data.success = false; return data; } else if (result.startsWith("waiting:")) { publishProgress(new Object[] {R.string.upgrade_decks_upload, result.split(":")[1]}); } else if (result.equals("upgrading")) { publishProgress(new Object[] {R.string.upgrade_decks_upgrade_started}); } else if (result.equals("ready")) { break; } else { data.success = false; return data; } Thread.sleep(1000); } // step 4: fetch upgraded file. this will return the .anki2 file directly, with // gzip compression if the client says it can handle it publishProgress(new Object[] {R.string.upgrade_decks_downloading}); resp = h.req("upgrade/download?key=" + key); if (resp == null) { data.success = false; return data; } // step 5: check the received file is valid InputStream cont = resp.getEntity().getContent(); if (!h.writeToFile(cont, colFilename)) { data.success = false; return data; } // check the received file is ok publishProgress(new Object[] {R.string.sync_check_download_file}); publishProgress(R.string.sync_check_download_file); try { AnkiDb d = AnkiDatabaseManager.getDatabase(colFilename); if (!d.queryString("PRAGMA integrity_check").equalsIgnoreCase("ok")) { data.success = false; return data; } } finally { AnkiDatabaseManager.closeDatabase(colFilename); } data.success = true; return data; } catch (FileNotFoundException e) { throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (IllegalStateException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } finally { (new File(zipFilename)).delete(); } }