<commit_before>package net.af0.sesame;

import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteException;
import android.util.Base64;
import android.util.Log;

import com.google.protobuf.InvalidProtocolBufferException;

import net.sqlcipher.Cursor;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteDatabaseHook;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Singleton class wrapping the database interactions.
 */
public final class SQLCipherDatabase {
    static final String COLUMN_USERNAME = "username";
    static final String COLUMN_DOMAIN = "domain";
    private static final String TABLE_KEYS = "keys";
    private static final String COLUMN_ID = "id";
    private static final String COLUMN_PASSWORD = "password";
    private static final String COLUMN_REMARKS = "remarks";
    static final String DATABASE_CREATE = "create table " +
            TABLE_KEYS + "(" +
            COLUMN_ID + " integer primary key autoincrement, " +
            COLUMN_USERNAME + " text, " +
            COLUMN_DOMAIN + " text," +
            COLUMN_PASSWORD + " text," +
            COLUMN_REMARKS + " text);";
    private static final String[] allColumns_ = {
            // Stupid. I named the field "id" and SimpleCursorFactory expects "id". Rather than
            // rename and break compatibility to gracefully handle database upgrades, let's just
            // alias it. I hope nobody is reading this...
            COLUMN_ID + " AS _id", COLUMN_USERNAME, COLUMN_DOMAIN,
            COLUMN_PASSWORD, COLUMN_REMARKS};
    private static final int DATABASE_VERSION = 3;
    private static final String DATABASE_NAME = "keys.db";
    private static net.sqlcipher.database.SQLiteOpenHelper helper_;
    private static SQLiteDatabase database_;

    public static Record createRecord(String username, String domain, String password,
                                      String remarks) {
        ContentValues values = new ContentValues();
        values.put(COLUMN_USERNAME, username);
        values.put(COLUMN_DOMAIN, domain);
        values.put(COLUMN_PASSWORD, password);
        values.put(COLUMN_REMARKS, remarks);
        long id = database_.insert(TABLE_KEYS, null, values);
        Cursor crs = database_.query(TABLE_KEYS, allColumns_,
                COLUMN_ID + "=" + id, null, null, null, null);
        crs.moveToFirst();
        Record r = toRecord(crs);
        crs.close();
        return r;
    }

    public static void deleteRecord(Record r) {
        database_.delete(TABLE_KEYS, COLUMN_ID + "=" + r.getId(), null);
    }

    public static void updateRecord(Record r) {
        ContentValues values = new ContentValues();
        values.put(COLUMN_USERNAME, r.getUsername());
        values.put(COLUMN_DOMAIN, r.getDomain());
        values.put(COLUMN_PASSWORD, <PASSWORD>());
        values.put(COLUMN_REMARKS, r.getRemarks());
        database_.update(TABLE_KEYS, values, COLUMN_ID + "=" + r.getId(), null);
    }

    public static Record getRecord(long record_id) {
        Cursor crs = database_.query(TABLE_KEYS, allColumns_,
                COLUMN_ID + "=" + record_id,
                null, null, null, null);
        if (crs.getCount() == 0) {
            return null;
        }
        crs.moveToFirst();
        Record r = toRecord(crs);
        crs.close();
        return r;
    }

    public static int getCount() {
        if (isLocked()) {
            return -1;
        }
        Cursor crs = database_.rawQuery("SELECT COUNT(*) FROM " + TABLE_KEYS, null);
        crs.moveToFirst();
        int r = crs.getInt(0);
        crs.close();
        return r;
    }

    public static Cursor getAllCursor() {
        return database_.query(TABLE_KEYS, allColumns_, null, null, null, null,
                COLUMN_DOMAIN + ", " + COLUMN_USERNAME + " COLLATE NOCASE DESC");
    }

    public static Cursor getContaining(String substr) {
        String s = DatabaseUtils.sqlEscapeString("%" + substr + "%");
        return database_.query(TABLE_KEYS, allColumns_,
                String.format("%s LIKE %s OR %s LIKE %s", COLUMN_DOMAIN, s, COLUMN_USERNAME, s),
                null, null, null, COLUMN_DOMAIN + ", " + COLUMN_USERNAME + " COLLATE NOCASE DESC");
    }

    public static Record toRecord(Cursor crs) {
        Record r = new Record();
        r.setId(crs.getLong(0));
        r.setUsername(crs.getString(1));
        r.setDomain(crs.getString(2));
        r.setPassword(crs.getString(3));
        r.setRemarks(crs.getString(4));

        return r;
    }

    private static DatabaseMetadata.Database getMetadataFromPrefs(Context ctx) {
        DatabaseMetadata.Database metadata = DatabaseMetadata.Database.newBuilder()
                .setVersion(DATABASE_VERSION)
                .setKdfIter(Constants.KDF_ITER)
                .build();
        SharedPreferences prefs = ctx.getSharedPreferences(Constants.DB_METADATA_PREF,
                Context.MODE_PRIVATE);
        try {
            metadata =
                    DatabaseMetadata.Database.newBuilder(metadata).mergeFrom(
                            DatabaseMetadata.Database.parseFrom(Base64.decode(prefs.getString(
                                            Constants.DB_METADATA_PREF, ""),<commit_msg>Really fix case-insensitive sorting this time.<commit_after>package net.af0.sesame;

import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteException;
import android.util.Base64;
import android.util.Log;

import com.google.protobuf.InvalidProtocolBufferException;

import net.sqlcipher.Cursor;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteDatabaseHook;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Singleton class wrapping the database interactions.
 */
public final class SQLCipherDatabase {
    static final String COLUMN_USERNAME = "username";
    static final String COLUMN_DOMAIN = "domain";
    private static final String TABLE_KEYS = "keys";
    private static final String COLUMN_ID = "id";
    private static final String COLUMN_PASSWORD = "password";
    private static final String COLUMN_REMARKS = "remarks";
    static final String DATABASE_CREATE = "create table " +
            TABLE_KEYS + "(" +
            COLUMN_ID + " integer primary key autoincrement, " +
            COLUMN_USERNAME + " text, " +
            COLUMN_DOMAIN + " text," +
            COLUMN_PASSWORD + " text," +
            COLUMN_REMARKS + " text);";
    private static final String[] allColumns_ = {
            // Stupid. I named the field "id" and SimpleCursorFactory expects "id". Rather than
            // rename and break compatibility to gracefully handle database upgrades, let's just
            // alias it. I hope nobody is reading this...
            COLUMN_ID + " AS _id", COLUMN_USERNAME, COLUMN_DOMAIN,
            COLUMN_PASSWORD, COLUMN_REMARKS};
    private static final int DATABASE_VERSION = 3;
    private static final String DATABASE_NAME = "keys.db";
    private static net.sqlcipher.database.SQLiteOpenHelper helper_;
    private static SQLiteDatabase database_;

    public static Record createRecord(String username, String domain, String password,
                                      String remarks) {
        ContentValues values = new ContentValues();
        values.put(COLUMN_USERNAME, username);
        values.put(COLUMN_DOMAIN, domain);
        values.put(COLUMN_PASSWORD, password);
        values.put(COLUMN_REMARKS, remarks);
        long id = database_.insert(TABLE_KEYS, null, values);
        Cursor crs = database_.query(TABLE_KEYS, allColumns_,
                COLUMN_ID + "=" + id, null, null, null, null);
        crs.moveToFirst();
        Record r = toRecord(crs);
        crs.close();
        return r;
    }

    public static void deleteRecord(Record r) {
        database_.delete(TABLE_KEYS, COLUMN_ID + "=" + r.getId(), null);
    }

    public static void updateRecord(Record r) {
        ContentValues values = new ContentValues();
        values.put(COLUMN_USERNAME, r.getUsername());
        values.put(COLUMN_DOMAIN, r.getDomain());
        values.put(COLUMN_PASSWORD, r.getPassword());
        values.put(COLUMN_REMARKS, r.getRemarks());
        database_.update(TABLE_KEYS, values, COLUMN_ID + "=" + r.getId(), null);
    }

    public static Record getRecord(long record_id) {
        Cursor crs = database_.query(TABLE_KEYS, allColumns_,
                COLUMN_ID + "=" + record_id,
                null, null, null, null);
        if (crs.getCount() == 0) {
            return null;
        }
        crs.moveToFirst();
        Record r = toRecord(crs);
        crs.close();
        return r;
    }

    public static int getCount() {
        if (isLocked()) {
            return -1;
        }
        Cursor crs = database_.rawQuery("SELECT COUNT(*) FROM " + TABLE_KEYS, null);
        crs.moveToFirst();
        int r = crs.getInt(0);
        crs.close();
        return r;
    }

    public static Cursor getAllCursor() {
        return database_.query(TABLE_KEYS, allColumns_, null, null, null, null,
                COLUMN_DOMAIN + "COLLATE NOCASE ASC, " + COLUMN_USERNAME + " COLLATE NOCASE ASC");
    }

    public static Cursor getContaining(String substr) {
        String s = DatabaseUtils.sqlEscapeString("%" + substr + "%");
        return database_.query(TABLE_KEYS, allColumns_,
                String.format("%s LIKE %s OR %s LIKE %s", COLUMN_DOMAIN, s, COLUMN_USERNAME, s),
                null, null, null, COLUMN_DOMAIN + "COLLATE NOCASE ASC, " + COLUMN_USERNAME +
                        " COLLATE NOCASE ASC"
        );
    }

    public static Record toRecord(Cursor crs) {
        Record r = new Record();
        r.setId(crs.getLong(0));
        r.setUsername(crs.getString(1));
        r.setDomain(crs.getString(2));
        r.setPassword(crs.getString(3));
        r.setRemarks(crs.getString(4));

        return r;
    }

    private static DatabaseMetadata.Database getMetadataFromPrefs(Context ctx) {
        DatabaseMetadata.Database metadata = DatabaseMetadata.Database.newBuilder()
                .setVersion(DATABASE_VERSION)
                .setKdfIter(Constants.KDF_ITER)
                .build();
        SharedPreferences prefs = ctx.getSharedPreferences(Constants.DB_METADATA_PREF,
                Context.MODE_PRIVATE);
        try {
            metadata =
                    DatabaseMetadata.Database.newBuilder(metadata).mergeFrom(
                            DatabaseMetadata.Database.parseFrom(Base64.decode(prefs.getString(
                                            Constants.DB_METADATA_PREF, ""),
{'fraction_non_alphanumeric': 0.10042330901558137, 'fraction_numerical': 0.002521840943889039, 'mean_word_length': 2.5318066157760812, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5215710', 'n_tokens_mistral': 3189, 'n_tokens_neox': 3171, 'n_words': 753}