예제 #1
0
 private void undoredo(Stack<UndoRow> src, Stack<UndoRow> dst) {
   UndoRow row;
   commitToDB();
   while (true) {
     row = src.pop();
     if (row != null) break;
   }
   Long start = row.start;
   Long end = row.end;
   if (end == null) end = latestUndoRow();
   ArrayList<String> sql =
       AnkiDb.queryColumn(
           String.class,
           String.format(
               ENGLISH_LOCALE,
               "SELECT sql FROM undoLog " + "WHERE seq > %d and seq <= %d " + "ORDER BY seq DESC",
               start,
               end),
           0);
   Long newstart = latestUndoRow();
   Iterator<String> iter = sql.iterator();
   while (iter.hasNext()) AnkiDb.database.execSQL(iter.next());
   Long newend = latestUndoRow();
   dst.push(new UndoRow(row.name, newstart, newend));
 }
예제 #2
0
 private long getRevCard() {
   long id;
   try {
     id = AnkiDb.queryScalar("SELECT id " + "FROM " + revCardTable() + "LIMIT 1");
   } catch (Exception e) {
     return 0;
   }
   return id;
 }
예제 #3
0
  public static double getLastModified(String deckPath) {
    double value;
    Cursor cursor = null;
    // Log.i(TAG, "Deck - getLastModified from deck = " + deckPath);
    AnkiDb.openDatabase(deckPath);

    try {
      cursor = AnkiDb.database.rawQuery("SELECT modified" + " FROM decks" + " LIMIT 1", null);

      if (!cursor.moveToFirst()) value = -1;
      else value = cursor.getDouble(0);
    } finally {
      if (cursor != null) cursor.close();
    }

    AnkiDb.closeDatabase();
    return value;
  }
예제 #4
0
 private long latestUndoRow() {
   long result;
   try {
     result = AnkiDb.queryScalar("SELECT MAX(rowid) FROM undoLog");
   } catch (SQLException e) {
     result = 0;
   }
   return result;
 }
예제 #5
0
 private long getCardId() {
   long id;
   // Failed card due?
   if ((delay0 != 0) && (failedNowCount != 0))
     return AnkiDb.queryScalar("SELECT id FROM failedCards LIMIT 1");
   // Failed card queue too big?
   if ((failedCardMax != 0) && (failedSoonCount >= failedCardMax))
     return AnkiDb.queryScalar("SELECT id FROM failedCards LIMIT 1");
   // Distribute new cards?
   if (timeForNewCard()) {
     id = maybeGetNewCard();
     if (id != 0) return id;
   }
   // Card due for review?
   if (revCount != 0) return getRevCard();
   // New cards left?
   id = maybeGetNewCard();
   if (id != 0) return id;
   // Review ahead?
   if (reviewEarly) {
     id = getCardIdAhead();
     if (id != 0) return id;
     else {
       resetAfterReviewEarly();
       checkDue();
     }
   }
   // Display failed cards early/last
   if (showFailedLast()) {
     try {
       id = AnkiDb.queryScalar("SELECT id FROM failedCards LIMIT 1");
     } catch (Exception e) {
       return 0;
     }
     return id;
   }
   return 0;
 }
예제 #6
0
 private long getCardIdAhead() {
   long id = 0;
   try {
     id =
         AnkiDb.queryScalar(
             "SELECT id "
                 + "FROM cards "
                 + "WHERE type = 1 and "
                 + "isDue = 0 and "
                 + "priority in (1,2,3,4) "
                 + "ORDER BY combinedDue "
                 + "LIMIT 1");
   } catch (SQLException e) {
     return 0;
   }
   return id;
 }
예제 #7
0
 private boolean timeForNewCard() {
   if (newCardSpacing == NEW_CARDS_LAST) return false;
   if (newCardSpacing == NEW_CARDS_FIRST) return true;
   // Force old if there are very high priority cards
   try {
     AnkiDb.queryScalar(
         "SELECT 1 "
             + "FROM cards "
             + "WHERE type = 1 and "
             + "isDue = 1 and "
             + "priority = 4 "
             + "LIMIT 1");
   } catch (Exception e) { // No result from query.
     if (newCardModulus == 0) return false;
     else return (dailyStats.reps % newCardModulus) == 0;
   }
   return false;
 }
예제 #8
0
  private void rebuildCounts(boolean full) {
    Log.i(TAG, "rebuildCounts - Rebuilding global and due counts...");
    // Need to check due first, so new due cards are not added later
    checkDue();
    // Global counts
    if (full) {
      cardCount = (int) AnkiDb.queryScalar("SELECT count(id) FROM cards");
      factCount = (int) AnkiDb.queryScalar("SELECT count(id) FROM facts");
    }

    // Due counts
    failedSoonCount = (int) AnkiDb.queryScalar("SELECT count(id) FROM failedCards");
    failedNowCount =
        (int)
            AnkiDb.queryScalar(
                "SELECT count(id) "
                    + "FROM cards "
                    + "WHERE type = 0 and "
                    + "isDue = 1 and "
                    + "combinedDue <= "
                    + String.format(
                        ENGLISH_LOCALE, "%f", (double) (System.currentTimeMillis() / 1000.0)));
    revCount =
        (int)
            AnkiDb.queryScalar(
                "SELECT count(id) "
                    + "FROM cards "
                    + "WHERE type = 1 and "
                    + "priority in (1,2,3,4) and "
                    + "isDue = 1");
    newCount =
        (int)
            AnkiDb.queryScalar(
                "SELECT count(id) "
                    + "FROM cards "
                    + "WHERE type = 2 and "
                    + "priority in (1,2,3,4) and "
                    + "isDue = 1");
  }
예제 #9
0
 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();
   }
 }
예제 #10
0
  /** Mark expired cards due and update counts. */
  private void checkDue() {
    Log.i(TAG, "Checking due cards...");
    checkDailyStats();

    // Failed cards
    ContentValues val = new ContentValues(1);
    val.put("isDue", 1);

    failedSoonCount +=
        AnkiDb.database.update(
            "cards",
            val,
            "type = 0 and "
                + "isDue = 0 and "
                + "priority in (1,2,3,4) and "
                + String.format(
                    ENGLISH_LOCALE,
                    "combinedDue <= %f",
                    (double) ((System.currentTimeMillis() / 1000.0) + delay0)),
            null);

    failedNowCount =
        (int)
            AnkiDb.queryScalar(
                "SELECT count(id) "
                    + "FROM cards "
                    + "WHERE type = 0 and "
                    + "isDue = 1 and "
                    + String.format(
                        ENGLISH_LOCALE,
                        "combinedDue <= %f",
                        (double) (System.currentTimeMillis() / 1000.0)));

    // Review
    val.clear();
    val.put("isDue", 1);
    revCount +=
        AnkiDb.database.update(
            "cards",
            val,
            "type = 1 and "
                + "isDue = 0 and "
                + "priority in (1,2,3,4) and "
                + String.format(
                    ENGLISH_LOCALE,
                    "combinedDue <= %f",
                    (double) (System.currentTimeMillis() / 1000.0)),
            null);

    // New
    val.clear();
    val.put("isDue", 1);
    newCount +=
        AnkiDb.database.update(
            "cards",
            val,
            "type = 2 and "
                + "isDue = 0 and "
                + "priority in (1,2,3,4) and "
                + String.format(
                    ENGLISH_LOCALE,
                    "combinedDue <= %f",
                    (double) (System.currentTimeMillis() / 1000.0)),
            null);

    newCountToday = Math.max(Math.min(newCount, newCardsPerDay - newCardsToday()), 0);
  }
예제 #11
0
 public void closeDeck() {
   DeckTask.waitToFinish(); // Wait for any thread working on the deck to finish.
   if (modifiedSinceSave()) commitToDB();
   AnkiDb.closeDatabase();
 }
예제 #12
0
  public static Deck openDeck(String path) throws SQLException {
    Deck deck = null;
    Cursor cursor = null;
    Log.i(TAG, "openDeck - Opening database " + path);
    AnkiDb.openDatabase(path);

    try {
      // Read in deck table columns
      cursor = AnkiDb.database.rawQuery("SELECT *" + " FROM decks" + " LIMIT 1", null);

      if (!cursor.moveToFirst()) return null;

      deck = new Deck();
      deck.id = cursor.getLong(0);
      deck.created = cursor.getDouble(1);
      deck.modified = cursor.getDouble(2);
      deck.description = cursor.getString(3);
      deck.version = cursor.getInt(4);
      deck.currentModelId = cursor.getLong(5);
      deck.syncName = cursor.getString(6);
      deck.lastSync = cursor.getDouble(7);
      deck.hardIntervalMin = cursor.getDouble(8);
      deck.hardIntervalMax = cursor.getDouble(9);
      deck.midIntervalMin = cursor.getDouble(10);
      deck.midIntervalMax = cursor.getDouble(11);
      deck.easyIntervalMin = cursor.getDouble(12);
      deck.easyIntervalMax = cursor.getDouble(13);
      deck.delay0 = cursor.getDouble(14);
      deck.delay1 = cursor.getDouble(15);
      deck.delay2 = cursor.getDouble(16);
      deck.collapseTime = cursor.getDouble(17);
      deck.highPriority = cursor.getString(18);
      deck.medPriority = cursor.getString(19);
      deck.lowPriority = cursor.getString(20);
      deck.suspended = cursor.getString(21);
      deck.newCardOrder = cursor.getInt(22);
      deck.newCardSpacing = cursor.getInt(23);
      deck.failedCardMax = cursor.getInt(24);
      deck.newCardsPerDay = cursor.getInt(25);
      deck.sessionRepLimit = cursor.getInt(26);
      deck.sessionTimeLimit = cursor.getInt(27);
      deck.utcOffset = cursor.getDouble(28);
      deck.cardCount = cursor.getInt(29);
      deck.factCount = cursor.getInt(30);
      deck.failedNowCount = cursor.getInt(31);
      deck.failedSoonCount = cursor.getInt(32);
      deck.revCount = cursor.getInt(33);
      deck.newCount = cursor.getInt(34);
      deck.revCardOrder = cursor.getInt(35);

      Log.i(TAG, "openDeck - Read " + cursor.getColumnCount() + " columns from decks table.");
    } finally {
      if (cursor != null) cursor.close();
    }
    Log.i(
        TAG,
        String.format(
            ENGLISH_LOCALE,
            "openDeck - modified: %f currentTime: %f",
            deck.modified,
            System.currentTimeMillis() / 1000.0));

    deck.initVars();

    // Ensure necessary indices are available
    deck.updateDynamicIndices();
    // Save counts to determine if we should save deck after check
    int oldCount = deck.failedSoonCount + deck.revCount + deck.newCount;
    // Update counts
    deck.rebuildQueue();

    try {
      // Unsuspend reviewed early & buried
      cursor =
          AnkiDb.database.rawQuery(
              "SELECT id "
                  + "FROM cards "
                  + "WHERE type in (0,1,2) and "
                  + "isDue = 0 and "
                  + "priority in (-1,-2)",
              null);

      if (cursor.moveToFirst()) {
        int count = cursor.getCount();
        long[] ids = new long[count];
        for (int i = 0; i < count; i++) {
          ids[i] = cursor.getLong(0);
          cursor.moveToNext();
        }
        deck.updatePriorities(ids);
        deck.checkDue();
      }
    } finally {
      if (cursor != null) cursor.close();
    }

    // Save deck to database if it has been modified
    if ((oldCount != (deck.failedSoonCount + deck.revCount + deck.newCount))
        || deck.modifiedSinceSave()) deck.commitToDB();

    // Create a temporary view for random new cards. Randomizing the cards by themselves
    // as is done in desktop Anki in Deck.randomizeNewCards() takes too long.
    AnkiDb.database.execSQL(
        "CREATE TEMPORARY VIEW acqCardsRandom AS "
            + "SELECT * FROM cards "
            + "WHERE type = 2 AND isDue = 1 "
            + "ORDER BY RANDOM()");

    return deck;
  }
예제 #13
0
  private void initUndo() {
    undoStack = new Stack<UndoRow>();
    redoStack = new Stack<UndoRow>();
    undoEnabled = true;

    AnkiDb.database.execSQL(
        "CREATE TEMPORARY TABLE undoLog (seq INTEGER PRIMARY KEY NOT NULL, sql TEXT)");

    ArrayList<String> tables =
        AnkiDb.queryColumn(String.class, "SELECT name FROM sqlite_master WHERE type = 'table'", 0);
    Iterator<String> iter = tables.iterator();
    while (iter.hasNext()) {
      String table = iter.next();
      if (table.equals("undoLog") || table.equals("sqlite_stat1")) continue;
      ArrayList<String> columns =
          AnkiDb.queryColumn(String.class, "PRAGMA TABLE_INFO(" + table + ")", 1);
      // Insert trigger
      String sql =
          "CREATE TEMP TRIGGER _undo_%s_it "
              + "AFTER INSERT ON %s BEGIN "
              + "INSERT INTO undoLog VALUES "
              + "(null, 'DELETE FROM %s WHERE rowid = ' || new.rowid); END";
      AnkiDb.database.execSQL(String.format(ENGLISH_LOCALE, sql, table, table, table));
      // Update trigger
      sql =
          String.format(
              ENGLISH_LOCALE,
              "CREATE TEMP TRIGGER _undo_%s_ut "
                  + "AFTER UPDATE ON %s BEGIN "
                  + "INSERT INTO undoLog VALUES "
                  + "(null, 'UPDATE %s ",
              table,
              table,
              table);
      String sep = "SET ";
      Iterator<String> columnIter = columns.iterator();
      while (columnIter.hasNext()) {
        String column = columnIter.next();
        if (column.equals("unique")) continue;
        sql += String.format(ENGLISH_LOCALE, "%s%s=' || quote(old.%s) || '", sep, column, column);
        sep = ",";
      }
      sql += "WHERE rowid = ' || old.rowid); END";
      AnkiDb.database.execSQL(sql);
      // Delete trigger
      sql =
          String.format(
              ENGLISH_LOCALE,
              "CREATE TEMP TRIGGER _undo_%s_dt "
                  + "BEFORE DELETE ON %s BEGIN "
                  + "INSERT INTO undoLog VALUES "
                  + "(null, 'INSERT INTO %s (rowid",
              table,
              table,
              table);
      columnIter = columns.iterator();
      while (columnIter.hasNext()) {
        String column = columnIter.next();
        sql += String.format(ENGLISH_LOCALE, ",\"%s\"", column);
      }
      sql += ") VALUES (' || old.rowid ||'";
      columnIter = columns.iterator();
      while (columnIter.hasNext()) {
        String column = columnIter.next();
        if (column.equals("unique")) {
          sql += ",1";
          continue;
        }
        sql += String.format(ENGLISH_LOCALE, ", ' || quote(old.%s) ||'", column);
      }
      sql += ")'); END";
      AnkiDb.database.execSQL(sql);
    }
  }