package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context import android.database.Cursor import android.net.Uri import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import java.lang.IllegalArgumentException import java.util.* import kotlin.collections.ArrayList /** * Keeps track of the backup files saved by the app. * Uses [BackupFileRecord] as an entry data projection. */ class LokiBackupFilesDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Database(context, databaseHelper) { companion object { public const val TABLE_NAME = "backup_files" private const val COLUMN_ID = "_id" private const val COLUMN_URI = "uri" private const val COLUMN_FILE_SIZE = "file_size" private const val COLUMN_TIMESTAMP = "timestamp" private val allColumns = arrayOf(COLUMN_ID, COLUMN_URI, COLUMN_FILE_SIZE, COLUMN_TIMESTAMP) @JvmStatic val createTableCommand = """ CREATE TABLE $TABLE_NAME ( $COLUMN_ID INTEGER PRIMARY KEY, $COLUMN_URI TEXT NOT NULL, $COLUMN_FILE_SIZE INTEGER NOT NULL, $COLUMN_TIMESTAMP INTEGER NOT NULL ); """.trimIndent() private fun mapCursorToRecord(cursor: Cursor): BackupFileRecord { val id = cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_ID)) val uriRaw = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_URI)) val fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_FILE_SIZE)) val timestampRaw = cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_TIMESTAMP)) return BackupFileRecord(id, Uri.parse(uriRaw), fileSize, Date(timestampRaw)) } private fun mapRecordToValues(record: BackupFileRecord): ContentValues { val contentValues = ContentValues() if (record.id >= 0) { contentValues.put(COLUMN_ID, record.id) } contentValues.put(COLUMN_URI, record.uri.toString()) contentValues.put(COLUMN_FILE_SIZE, record.fileSize) contentValues.put(COLUMN_TIMESTAMP, record.timestamp.time) return contentValues } } fun getBackupFiles(): List { databaseHelper.readableDatabase.query(TABLE_NAME, allColumns, null, null, null, null, null).use { val records = ArrayList() while (it != null && it.moveToNext()) { val record = mapCursorToRecord(it) records.add(record) } return records } } fun insertBackupFile(record: BackupFileRecord): BackupFileRecord { val contentValues = mapRecordToValues(record) val id = databaseHelper.writableDatabase.insertOrThrow(TABLE_NAME, null, contentValues) return BackupFileRecord(id, record.uri, record.fileSize, record.timestamp) } fun getLastBackupFileTime(): Date? { // SELECT $COLUMN_TIMESTAMP FROM $TABLE_NAME ORDER BY $COLUMN_TIMESTAMP DESC LIMIT 1 databaseHelper.readableDatabase.query( TABLE_NAME, arrayOf(COLUMN_TIMESTAMP), null, null, null, null, "$COLUMN_TIMESTAMP DESC", "1" ).use { if (it !== null && it.moveToFirst()) { return Date(it.getLong(0)) } else { return null } } } fun getLastBackupFile(): BackupFileRecord? { // SELECT * FROM $TABLE_NAME ORDER BY $COLUMN_TIMESTAMP DESC LIMIT 1 databaseHelper.readableDatabase.query( TABLE_NAME, allColumns, null, null, null, null, "$COLUMN_TIMESTAMP DESC", "1" ).use { if (it != null && it.moveToFirst()) { return mapCursorToRecord(it) } else { return null } } } fun deleteBackupFile(record: BackupFileRecord): Boolean { return deleteBackupFile(record.id) } fun deleteBackupFile(id: Long): Boolean { if (id < 0) { throw IllegalArgumentException("ID must be zero or a positive number.") } return databaseHelper.writableDatabase.delete(TABLE_NAME, "$COLUMN_ID = $id", null) > 0 } }