Skip to content

Commit 04f9545

Browse files
committed
Moving from reading CSVs to Sqlite3 for performance reasons during read. Currently, the generation of the database is at install, inefficient, will move later to ship as is.
1 parent b48881f commit 04f9545

3 files changed

Lines changed: 188 additions & 50 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package fr.berliat.hskwidget.data.store
2+
3+
import android.content.ContentValues
4+
import android.content.Context
5+
import android.database.sqlite.SQLiteDatabase
6+
import android.database.sqlite.SQLiteOpenHelper
7+
import android.util.Log
8+
import com.opencsv.CSVReader
9+
import fr.berliat.hskwidget.data.model.ChineseWord
10+
import java.util.Locale
11+
12+
class ChineseWordsDBHelper(var context: Context) :
13+
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
14+
override fun onCreate(db: SQLiteDatabase) {
15+
Log.i("ChineseWordsDBHelper", "Creating Database")
16+
db.execSQL(CREATE_TABLE)
17+
18+
getDictFromCSV().forEach {
19+
val row = ContentValues()
20+
row.put(SIMPLIFIED, it.simplified)
21+
row.put(DEFINITION_EN, it.definition[Locale.ENGLISH])
22+
row.put(HSK, it.HSK.level)
23+
row.put(PINYINS, it.pinyins.toString())
24+
db.insert(TABLE_NAME, null, row)
25+
}
26+
}
27+
28+
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
29+
Log.i("ChineseWordsDBHelper", "Upgrading Database")
30+
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME)
31+
onCreate(db)
32+
}
33+
34+
fun getDictFromCSV(): Array<ChineseWord> {
35+
Log.i("ChineseWordsStore", "Dictionary is being loaded from disk")
36+
37+
val fullDict = mutableListOf<ChineseWord>()
38+
ChineseWord.HSK_Level.values().forEach {
39+
addFromCSVResource(it, fullDict, getHSKFile(it))
40+
}
41+
42+
return fullDict.toTypedArray()
43+
}
44+
45+
private fun addFromCSVResource(
46+
hsk: ChineseWord.HSK_Level, fullDict: MutableList<ChineseWord>,
47+
reader: CSVReader
48+
) {
49+
var nextLine: Array<String>?
50+
nextLine = reader.readNext()
51+
while (nextLine != null) {
52+
fullDict.add(
53+
ChineseWord(
54+
nextLine[0],
55+
"",
56+
mapOf(Locale.ENGLISH to nextLine[2]),
57+
hsk,
58+
ChineseWord.Pinyins(nextLine[1])
59+
)
60+
)
61+
62+
nextLine = reader.readNext()
63+
}
64+
}
65+
66+
private fun getHSKFile(hskLevel: ChineseWord.HSK_Level): CSVReader {
67+
return CSVReader(context.assets.open("hsk_csk/hsk${hskLevel.level}.csv").reader())
68+
}
69+
70+
companion object {
71+
// Table Name
72+
const val TABLE_NAME = "hsk_words"
73+
74+
// Table columns
75+
const val _ID = "_id"
76+
const val SIMPLIFIED = "simplified"
77+
const val DEFINITION_EN = "definition_en"
78+
const val HSK = "hsk_level"
79+
const val PINYINS = "pinyin"
80+
81+
// Database Information
82+
const val DB_NAME = "chinesewords.db"
83+
84+
// database version
85+
const val DB_VERSION = 1
86+
87+
// Creating table query
88+
private const val CREATE_TABLE = ("CREATE TABLE " + TABLE_NAME + "("
89+
+ _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
90+
+ SIMPLIFIED + " TEXT NOT NULL, "
91+
+ DEFINITION_EN + " TEXT NOT NULL, "
92+
+ HSK + " INTEGER NOT NULL,"
93+
+ PINYINS + " TEXT NOT NULL);")
94+
}
95+
}
Lines changed: 88 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,66 @@
11
package fr.berliat.hskwidget.data.store
22

33
import android.content.Context
4+
import android.database.Cursor
45
import android.util.Log
5-
import com.opencsv.CSVReader
66
import fr.berliat.hskwidget.data.model.ChineseWord
77
import java.util.Locale
88

9-
class ChineseWordsStore private constructor(val context: Context) {
10-
private val fullDict = mutableListOf<ChineseWord>()
119

12-
init {
13-
Log.i("ChineseWordsStore", "A new dictionary is being loaded")
14-
ChineseWord.HSK_Level.values().forEach {
15-
addFromCSVResource(it, getHSKFile(it))
16-
}
10+
class ChineseWordsStore private constructor(val context: Context) {
11+
private val dbHelper = ChineseWordsDBHelper(context)
12+
private val database = dbHelper.readableDatabase
13+
14+
private val projection = arrayOf(
15+
ChineseWordsDBHelper.SIMPLIFIED,
16+
ChineseWordsDBHelper.PINYINS,
17+
ChineseWordsDBHelper.HSK,
18+
ChineseWordsDBHelper.DEFINITION_EN
19+
)
20+
21+
fun getOnlyHSKLevels(levels: Set<ChineseWord.HSK_Level>): Array<ChineseWord> {
22+
return _getOnlyHSKLevels(levels, arrayListOf(), "", "")
1723
}
1824

19-
fun getOnlyHSKLevels(levels: Set<ChineseWord.HSK_Level>) : List<ChineseWord> {
20-
return fullDict.filter {
21-
levels.contains(it.HSK)
25+
private fun _getOnlyHSKLevels(
26+
levels: Set<ChineseWord.HSK_Level>, bannedWord: ArrayList<ChineseWord>,
27+
orderBy: String, limit: String
28+
): Array<ChineseWord> {
29+
// Filter results WHERE "title" = 'My Title'
30+
val sel =
31+
"${ChineseWordsDBHelper.HSK} IN (" + levels.map { it.level }.joinToString() + ") " +
32+
"AND ${ChineseWordsDBHelper.SIMPLIFIED} NOT IN (?)"
33+
34+
val cursor = database.query(
35+
ChineseWordsDBHelper.TABLE_NAME, // The table to query
36+
projection, // The array of columns to return (pass null to get all)
37+
sel, // The columns for the WHERE clause
38+
bannedWord.map { it.simplified }.toTypedArray(), // The values for the WHERE clause
39+
null, // don't group the rows
40+
null, // don't filter by row groups
41+
orderBy,
42+
limit
43+
)
44+
45+
val dict = mutableSetOf<ChineseWord>()
46+
with(cursor) {
47+
while (moveToNext()) {
48+
dict.add(cursorToWord(cursor))
49+
}
2250
}
51+
cursor.close()
52+
53+
return dict.toTypedArray()
2354
}
2455

2556
fun getRandomWord(
2657
levels: Set<ChineseWord.HSK_Level>,
2758
bannedWords: ArrayList<ChineseWord>
2859
) : ChineseWord? {
29-
val dict = getOnlyHSKLevels(levels)
30-
if (dict.isEmpty() || dict.size == bannedWords.size) return null
60+
val dict = _getOnlyHSKLevels(levels, bannedWords, "RANDOM()", "1")
61+
if (dict.isEmpty()) return null
3162

32-
var word : ChineseWord
63+
var word: ChineseWord
3364
do {
3465
word = dict.random()
3566
} while (bannedWords.contains(word))
@@ -39,47 +70,60 @@ class ChineseWordsStore private constructor(val context: Context) {
3970
}
4071

4172
fun findWordFromSimplified(simplifiedWord: String?): ChineseWord? {
42-
val word = fullDict.filter {
43-
it.simplified == simplifiedWord
44-
}
45-
46-
if (word.isEmpty())
73+
// Filter results WHERE "title" = 'My Title'
74+
val simpSel = "${ChineseWordsDBHelper.SIMPLIFIED} IN (?)"
75+
76+
val cursor = database.query(
77+
ChineseWordsDBHelper.TABLE_NAME, // The table to query
78+
projection, // The array of columns to return (pass null to get all)
79+
simpSel, // The columns for the WHERE clause
80+
arrayOf(simplifiedWord), // The values for the WHERE clause
81+
null, // don't group the rows
82+
null, // don't filter by row groups
83+
""
84+
)
85+
86+
if (!cursor.moveToNext())
4787
return null
4888

49-
return word.first()
89+
val word = cursorToWord(cursor)
90+
cursor.close()
91+
92+
return word
5093
}
5194

52-
private fun addFromCSVResource(hsk: ChineseWord.HSK_Level, reader: CSVReader) {
53-
var nextLine: Array<String>?
54-
nextLine = reader.readNext()
55-
while (nextLine != null) {
56-
fullDict.add(
57-
ChineseWord(
58-
nextLine[0],
59-
"",
60-
mapOf(Locale.ENGLISH to nextLine[2]),
61-
hsk,
62-
ChineseWord.Pinyins(nextLine[1])
95+
private fun cursorToWord(cursor: Cursor): ChineseWord {
96+
with(ChineseWordsDBHelper) {
97+
return ChineseWord(
98+
cursor.getString(cursor.getColumnIndexOrThrow(SIMPLIFIED)),
99+
"",
100+
mapOf(
101+
Locale.ENGLISH to cursor.getString(
102+
cursor.getColumnIndexOrThrow(DEFINITION_EN)
103+
)
104+
),
105+
ChineseWord.HSK_Level.from(
106+
cursor.getInt(
107+
cursor.getColumnIndexOrThrow(HSK)
108+
)
109+
),
110+
ChineseWord.Pinyins(
111+
cursor.getString(
112+
cursor.getColumnIndexOrThrow(PINYINS)
113+
)
63114
)
64115
)
65-
66-
nextLine = reader.readNext()
67116
}
68117
}
69118

70-
private fun getHSKFile(hskLevel: ChineseWord.HSK_Level) : CSVReader {
71-
//ToDo: optimize into a database to avoid loading all just to pull a random word
72-
return CSVReader(context.assets.open("hsk_csk/hsk${hskLevel.level}.csv").reader())
73-
}
74-
75119
companion object {
76-
// @Todo: monitor for possible memory leak
120+
//Todo: Monitor for memory leak.
121+
@Volatile
77122
private var instance: ChineseWordsStore? = null
78123

79-
fun getInstance(context: Context) : ChineseWordsStore {
80-
if (instance == null) instance = ChineseWordsStore(context)
81-
82-
return instance!!
83-
}
124+
fun getInstance(context: Context) =
125+
instance ?: synchronized(this) {
126+
instance ?: ChineseWordsStore(context).also { instance = it }
127+
}
84128
}
85129
}

app/src/main/java/fr/berliat/hskwidget/domain/FlashcardManager.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,10 @@ class FlashcardManager private constructor(private val context: Context,
9797
companion object {
9898
private var instances = mutableMapOf<Int, FlashcardManager>()
9999

100-
fun getInstance(context: Context, widgetId: Int): FlashcardManager {
101-
if (instances[widgetId] == null)
102-
instances[widgetId] = FlashcardManager(context, widgetId)
103-
104-
return instances[widgetId]!!
105-
}
100+
fun getInstance(context: Context, widgetId: Int) =
101+
instances[widgetId] ?: synchronized(this) {
102+
instances[widgetId] ?: FlashcardManager(context, widgetId)
103+
.also { instances[widgetId] = it }
104+
}
106105
}
107106
}

0 commit comments

Comments
 (0)