Extensions.kt 8.12 KB
Newer Older
1
2
3
package com.frostnerd.smokescreen.database

import android.content.Context
4
import android.util.Base64
5
import androidx.annotation.VisibleForTesting
6
import androidx.fragment.app.Fragment
7
import androidx.room.Room
8
9
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
Daniel Wolf's avatar
Daniel Wolf committed
10
import com.frostnerd.smokescreen.Logger
11
12
13
import org.minidns.record.Record
import java.io.ByteArrayInputStream
import java.io.DataInputStream
14

Daniel Wolf's avatar
Daniel Wolf committed
15
16
/*
 * Copyright (C) 2019 Daniel Wolf (Ch4t4r)
17
 *
Daniel Wolf's avatar
Daniel Wolf committed
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * You can contact the developer at daniel.wolf@frostnerd.com.
32
33
 */

34
private var INSTANCE: AppDatabase? = null
35
36
@VisibleForTesting
val MIGRATION_2_X = migration(2) {
Daniel Wolf's avatar
Daniel Wolf committed
37
    Logger.logIfOpen("DB_MIGRATION", "Migrating from 2 to the current version (${AppDatabase.currentVersion}")
38
39
    it.execSQL("DROP TABLE CachedResponse")
    it.execSQL("CREATE TABLE CachedResponse(type INTEGER NOT NULL, dnsName TEXT NOT NULL, records TEXT NOT NULL, PRIMARY KEY(dnsName, type))")
Daniel Wolf's avatar
Daniel Wolf committed
40
    it.execSQL("DROP TABLE IF EXISTS UserServerConfiguration")
Daniel Wolf's avatar
Daniel Wolf committed
41
    MIGRATION_3_4.migrate(it)
42
    MIGRATION_5_6.migrate(it)
Daniel Wolf's avatar
Daniel Wolf committed
43
    Logger.logIfOpen("DB_MIGRATION", "Migration from 2 to current version completed")
Daniel Wolf's avatar
Daniel Wolf committed
44
}
45
46
@VisibleForTesting
val MIGRATION_3_4 = migration(3, 4) {
Daniel Wolf's avatar
Daniel Wolf committed
47
    Logger.logIfOpen("DB_MIGRATION", "Migrating from 3 to 4")
Daniel Wolf's avatar
Daniel Wolf committed
48
    it.execSQL("CREATE TABLE IF NOT EXISTS `DnsQuery` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `askedServer` TEXT, `fromCache` INTEGER NOT NULL, `questionTime` INTEGER NOT NULL, `responseTime` INTEGER NOT NULL, `responses` TEXT NOT NULL)")
Daniel Wolf's avatar
Daniel Wolf committed
49
    Logger.logIfOpen("DB_MIGRATION", "Migration from 3 to 4 completed")
50
}
51
52
@VisibleForTesting
val MIGRATION_4_5 = migration(4, 5) {
Daniel Wolf's avatar
Daniel Wolf committed
53
    Logger.logIfOpen("DB_MIGRATION", "Migrating from 4 to 5")
54
    it.execSQL("DROP TABLE IF EXISTS UserServerConfiguration")
Daniel Wolf's avatar
Daniel Wolf committed
55
    Logger.logIfOpen("DB_MIGRATION", "Migration from 4 to 5 completed")
56
}
57
58
@VisibleForTesting
val MIGRATION_5_6 = migration(5, 6) {
59
    Logger.logIfOpen("DB_MIGRATION", "Migrating from 5 to 6")
60
    it.execSQL("CREATE TABLE IF NOT EXISTS `DnsRule` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `stagingType` INTEGER NOT NULL, `type` INTEGER NOT NULL, `host` TEXT NOT NULL, `target` TEXT NOT NULL, `ipv6Target` TEXT, `importedFrom` INTEGER, FOREIGN KEY(`importedFrom`) REFERENCES `HostSource`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )")
61
    it.execSQL("CREATE TABLE IF NOT EXISTS `HostSource` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `enabled` INTEGER NOT NULL, `name` TEXT NOT NULL, `source` TEXT NOT NULL)")
62
    it.execSQL("CREATE INDEX IF NOT EXISTS `index_DnsRule_importedFrom` ON `DnsRule` (`importedFrom`)")
Daniel Wolf's avatar
Daniel Wolf committed
63
    it.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_DnsRule_host_type_stagingType` ON `DnsRule` (`host`, `type`, `stagingType`)")
64
65
    Logger.logIfOpen("DB_MIGRATION", "Migration from 5 to 6 completed")
}
66
67
@VisibleForTesting
val MIGRATION_6_7 = migration(6,7) {
Daniel Wolf's avatar
Daniel Wolf committed
68
    Logger.logIfOpen("DB_MIGRATION", "Migrating from 6 to 7")
69
70
    it.execSQL("DROP INDEX IF EXISTS `index_DnsRule_host`")
    it.execSQL("DROP INDEX IF EXISTS `index_DnsRule_host_type`")
71
    it.execSQL("DELETE FROM `DnsRule`")
72
    it.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_DnsRule_host_type_stagingType` ON `DnsRule` (`host`, `type`, `stagingType`)")
Daniel Wolf's avatar
Daniel Wolf committed
73
74
    Logger.logIfOpen("DB_MIGRATION", "Migration from 6 to 7 completed")
}
75
76
@VisibleForTesting
val MIGRATION_7_8 = migration(7,8) {
77
    Logger.logIfOpen("DB_MIGRATION", "Migrating from 7 to 8")
Daniel Wolf's avatar
Daniel Wolf committed
78
    it.execSQL("DROP TABLE IF EXISTS `DnsRule`")
79
    it.execSQL("CREATE TABLE IF NOT EXISTS `DnsRule` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `stagingType` INTEGER NOT NULL, `type` INTEGER NOT NULL, `host` TEXT NOT NULL, `target` TEXT NOT NULL, `ipv6Target` TEXT, `importedFrom` INTEGER, FOREIGN KEY(`importedFrom`) REFERENCES `HostSource`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )")
80
81
    it.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_DnsRule_host_type_stagingType` ON `DnsRule` (`host`, `type`, `stagingType`)")
    it.execSQL("CREATE INDEX IF NOT EXISTS `index_DnsRule_importedFrom` ON `DnsRule` (`importedFrom`)")
82
83
    Logger.logIfOpen("DB_MIGRATION", "Migration from 7 to 8 completed")
}
84
85
@VisibleForTesting
val MIGRATION_8_9 = migration(8, 9) {
86
87
    Logger.logIfOpen("DB_MIGRATION", "Migrating from 8 to 9")
    it.execSQL("ALTER TABLE `HostSource` ADD COLUMN `whitelistSource` INTEGER NOT NULL DEFAULT 0")
88
    it.execSQL("ALTER TABLE `HostSource` ADD COLUMN `ruleCount` INTEGER")
89
90
    Logger.logIfOpen("DB_MIGRATION", "Migration from 8 to 9 completed")
}
91
92
93
94
@VisibleForTesting
val MIGRATION_9_10 = migration(9, 10) {
    Logger.logIfOpen("DB_MIGRATION", "Migrating from 9 to 10")
    it.execSQL("ALTER TABLE `DnsRule` ADD COLUMN `isWildcard` INTEGER NOT NULL DEFAULT 0")
95
    it.execSQL("ALTER TABLE `HostSource` ADD COLUMN `checksum` TEXT DEFAULT NULL")
96
97
    Logger.logIfOpen("DB_MIGRATION", "Migration from 9 to 10 completed")
}
98

99
100
val MIGRATION_10_11 = migration(10, 11) {
    Logger.logIfOpen("DB_MIGRATION", "Migrating from 10 to 11")
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
    val count = it.query("SELECT COUNT(*) FROM 'DnsQuery'").let { cursor ->
        cursor.moveToFirst()
        val cnt = cursor.getInt(0)
        cursor.close()
        cnt
    }

    if(count > 0) {
        it.execSQL("CREATE TABLE `DnsQuery_tmp` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `askedServer` TEXT, `responseSource` TEXT NOT NULL, `questionTime` INTEGER NOT NULL, `responseTime` INTEGER NOT NULL, `responses` TEXT NOT NULL)")
        it.execSQL("INSERT INTO `DnsQuery_tmp`(id, type, name, askedServer, questionTime, responseTime, responses, responseSource) SELECT id, type, name, askedServer, questionTime, responseTime, responses, CASE WHEN fromCache=1 THEN 'CACHE' else 'UPSTREAM' END as `responseSource` FROM DnsQuery")
        it.execSQL("DROP TABLE `DnsQuery`")
        it.execSQL("ALTER TABLE `DnsQuery_tmp` RENAME TO `DnsQuery`")
    } else {
        it.execSQL("DROP TABLE `DnsQuery`")
        it.execSQL("CREATE TABLE `DnsQuery` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `askedServer` TEXT, `responseSource` TEXT NOT NULL, `questionTime` INTEGER NOT NULL, `responseTime` INTEGER NOT NULL, `responses` TEXT NOT NULL)")
    }

118
119
120
    Logger.logIfOpen("DB_MIGRATION", "Migration from 10 to 11 completed")
}

121
122
123
124
125

fun Context.getDatabase(): AppDatabase {
    if (INSTANCE == null) {
        INSTANCE = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "data")
            .allowMainThreadQueries()
126
            .addMigrations(MIGRATION_2_X, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10, MIGRATION_10_11)
127
            .build()
128
129
    }
    return INSTANCE!!
130
131
}

132
133
134
135
fun Fragment.getDatabase():AppDatabase {
    return context!!.getDatabase()
}

136
137
138
139
140
private fun migration(
    from: Int,
    to: Int = AppDatabase.currentVersion,
    migrate: (database: SupportSQLiteDatabase) -> Unit
): Migration {
141
    if(from < 0 || to >AppDatabase.currentVersion || from > to) throw IllegalStateException("Version out of bounds $from->$to with bounds 0 -- ${AppDatabase.currentVersion}")
142
143
144
145
146
    return object : Migration(from, to) {
        override fun migrate(database: SupportSQLiteDatabase) {
            migrate.invoke(database)
        }
    }
147
148
}

149
private fun emptyMigration(from: Int, to: Int = AppDatabase.currentVersion): Migration {
150
151
152
    return migration(from, to) { }
}

153
fun recordFromBase64(base64: String): Record<*> {
154
155
    val bytes = Base64.decode(base64, Base64.NO_WRAP)
    return Record.parse(DataInputStream(ByteArrayInputStream(bytes)), bytes)
156
}