1
0
Fork 0

New dictionary loader (#89)

* new, simpler (and hopefully, more efficient) dictionary loader

* no more dict.properties

* dictionaries are now validated during the build process

* TraditionalT9Settings code cleanup and code style improvements

* removed English, French, Italian, Russian repeating words

* removed invalid and repeating German words
This commit is contained in:
Dimo Karaivanov 2022-10-27 14:31:57 +03:00 committed by GitHub
parent 0ac7ec1790
commit 10099f1c37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 534 additions and 1855 deletions

1
.gitignore vendored
View file

@ -24,7 +24,6 @@ local.properties
*.log *.log
#Other #Other
assets/dict.properties
t9build.properties t9build.properties
*.keystore *.keystore

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,6 @@ ma
md md
mi mi
mo mo
ms
may may
mr mr
mrs mrs
@ -145,7 +144,6 @@ cow
cox cox
coy coy
cry cry
cs
cub cub
cue cue
cum cum
@ -236,7 +234,6 @@ gin
gnu gnu
go go
gob gob
god
goo goo
got got
gum gum
@ -278,7 +275,6 @@ hum
hut hut
ice ice
icy icy
id
if if
ifs ifs
ilk ilk
@ -336,14 +332,12 @@ lot
low low
lug lug
lye lye
ma
mad mad
man man
map map
mar mar
mas mas
mat mat
may
me me
men men
mes mes
@ -356,7 +350,6 @@ mom
moo moo
mop mop
mow mow
ms
mu mu
mud mud
mug mug
@ -396,7 +389,6 @@ old
on on
one one
opt opt
or
ore ore
our our
out out
@ -405,7 +397,6 @@ owe
owl owl
own own
ox ox
pa
pad pad
pal pal
pan pan

View file

@ -141541,9 +141541,7 @@ esquilles
Esquimau Esquimau
esquimaud esquimaud
esquimaude esquimaude
Esquimaude
esquimaudes esquimaudes
Esquimaudes
esquimauds esquimauds
esquimautage esquimautage
esquimautages esquimautages
@ -186709,7 +186707,6 @@ interneront
internes internes
internés internés
internet internet
Internet
internez internez
interniez interniez
internions internions
@ -215758,7 +215755,6 @@ naphtes
naphtol naphtol
naphtols naphtols
napoléon napoléon
Napoléon
napoléonien napoléonien
napoléonienne napoléonienne
napoléoniennes napoléoniennes

View file

@ -1707,7 +1707,6 @@ Courmayeur
Cremona Cremona
Cristina Cristina
Cristoforo Cristoforo
DOS
DVI DVI
Daniele Daniele
Dante Dante
@ -1740,7 +1739,6 @@ Euripide
Europa Europa
Eusebio Eusebio
Eva Eva
FAQ
FSF FSF
Fabio Fabio
Faenza Faenza
@ -102310,7 +102308,6 @@ tosavano
tosavate tosavate
tosavi tosavi
tosavo tosavo
toscana
toscane toscane
toscani toscani
toscano toscano

View file

@ -1,33 +1,25 @@
и
в
не не
на на
я
что что
быть быть
с
он он
а
это это
как как
то то
этот этот
по по
к
но но
они они
мы мы
она она
который который
из из
у
свой свой
вы вы
весь весь
за за
для для
от от
о
так так
мочь мочь
все все
@ -167,7 +159,6 @@
поэтому поэтому
почему почему
понимать понимать
москва
любой любой
однако однако
хорошо хорошо
@ -351,7 +342,6 @@
появиться появиться
хотеться хотеться
нельзя нельзя
д
белый белый
центр центр
опять опять
@ -400,7 +390,6 @@
быстро быстро
черный черный
сильный сильный
н
порядок порядок
чувствовать чувствовать
создать создать
@ -487,7 +476,6 @@
возможно возможно
принимать принимать
рубль рубль
б
миллион миллион
целый целый
приходить приходить
@ -529,7 +517,6 @@
пара пара
квартира квартира
забыть забыть
е
значение значение
внутренний внутренний
связать связать
@ -577,7 +564,6 @@
команда команда
добрый добрый
поле поле
г
сша сша
исследование исследование
общественный общественный
@ -591,7 +577,6 @@
сон сон
уходить уходить
служба служба
ж
население население
воздух воздух
словно словно
@ -616,7 +601,6 @@
орган орган
доллар доллар
держать держать
т
название название
похожий похожий
дорогой дорогой
@ -703,7 +687,6 @@
поздний поздний
песня песня
лучше лучше
п
случиться случиться
ум ум
выбор выбор
@ -785,7 +768,6 @@
чтоб чтоб
прошлый прошлый
столько столько
л
понятие понятие
длинный длинный
поддержка поддержка
@ -963,7 +945,6 @@
хватать хватать
вчера вчера
режим режим
р
очередной очередной
отдать отдать
здоровье здоровье
@ -1145,7 +1126,6 @@
утверждать утверждать
позвонить позвонить
реклама реклама
николай
революция революция
явление явление
отдел отдел
@ -1405,7 +1385,6 @@
бедный бедный
появление появление
издание издание
э
резко резко
поговорить поговорить
еврей еврей
@ -1436,7 +1415,6 @@
достигнуть достигнуть
сохранить сохранить
встретиться встретиться
ю
предполагать предполагать
глубина глубина
приказ приказ
@ -1552,7 +1530,6 @@
понятно понятно
уважение уважение
последствие последствие
ф
буква буква
изображение изображение
творческий творческий
@ -1595,7 +1572,6 @@
обед обед
переходить переходить
изучение изучение
олег
обеспечивать обеспечивать
заставлять заставлять
подниматься подниматься
@ -1826,7 +1802,6 @@
публикация публикация
кивнуть кивнуть
обсуждать обсуждать
х
наверняка наверняка
напомнить напомнить
логика логика
@ -1865,7 +1840,6 @@
кошка кошка
саша саша
характерный характерный
мария
толстый толстый
общаться общаться
пятый пятый
@ -2591,7 +2565,6 @@
набрать набрать
защитить защитить
остановка остановка
з
дорого дорого
какую-то какую-то
жестокий жестокий
@ -2779,7 +2752,6 @@
разнообразный разнообразный
отойти отойти
разделить разделить
ы
сборник сборник
глобальный глобальный
кафе кафе
@ -2816,7 +2788,6 @@
великолепный великолепный
доходить доходить
образовать образовать
наталья
разрушить разрушить
отражать отражать
лезть лезть
@ -3781,10 +3752,8 @@
симпатичный симпатичный
сделаться сделаться
продаваться продаваться
ш
направляться направляться
предупреждать предупреждать
ч
благоприятный благоприятный
палка палка
выдающийся выдающийся
@ -4016,7 +3985,6 @@
рыцарь рыцарь
дежурный дежурный
отрицать отрицать
светлана
выделяться выделяться
частица частица
динамика динамика
@ -4276,7 +4244,6 @@
рецепт рецепт
сан сан
махнуть махнуть
соня
смесь смесь
творение творение
кредитный кредитный
@ -4967,7 +4934,6 @@
привязать привязать
издатель издатель
предать предать
й
отставка отставка
заголовок заголовок
отступать отступать
@ -5538,7 +5504,6 @@
как-нибудь как-нибудь
превосходство превосходство
конфета конфета
ь
преподавать преподавать
ла ла
грамотный грамотный
@ -6238,7 +6203,6 @@
даша даша
армянский армянский
замерзнуть замерзнуть
вячеслав
фракция фракция
разноцветный разноцветный
гроза гроза
@ -6250,7 +6214,6 @@
хрупкий хрупкий
выпивать выпивать
напиться напиться
будда
октябрьский октябрьский
невыносимый невыносимый
сертификат сертификат
@ -6601,7 +6564,6 @@
грант грант
выраженный выраженный
слушаться слушаться
ц
расстроить расстроить
веселье веселье
жюри жюри
@ -7004,7 +6966,6 @@
алена алена
подсчет подсчет
профессионально профессионально
оксана
погладить погладить
рассыпаться рассыпаться
перебирать перебирать
@ -7631,7 +7592,6 @@
несомненный несомненный
многократно многократно
выгодно выгодно
санкт-петербург
украинец украинец
коварный коварный
аккумулятор аккумулятор
@ -8657,7 +8617,6 @@
авраам авраам
мэрия мэрия
фуражка фуражка
ё
прикол прикол
минуть минуть
яростно яростно
@ -11523,7 +11482,6 @@
кабинка кабинка
сатурн сатурн
байт байт
м
прямоугольный прямоугольный
остыть остыть
смягчить смягчить
@ -12768,7 +12726,6 @@
разделяться разделяться
конфликтный конфликтный
лыжи лыжи
ванька
обосноваться обосноваться
активизация активизация
основополагающий основополагающий
@ -13204,7 +13161,6 @@
затащить затащить
терапевтический терапевтический
чернокожий чернокожий
ъ
челнок челнок
моделировать моделировать
ограбление ограбление
@ -13526,7 +13482,6 @@
видеокарта видеокарта
выключатель выключатель
росток росток
щ
ослаблять ослаблять
безошибочно безошибочно
заикаться заикаться
@ -14528,7 +14483,6 @@
акцентировать акцентировать
высокотехнологичный высокотехнологичный
невозмутимый невозмутимый
Нина
железнодорожник железнодорожник
амулет амулет
дилемма дилемма
@ -15852,7 +15806,6 @@
никитич никитич
заманить заманить
вмещать вмещать
жигули
цыганский цыганский
поразиться поразиться
обсерватория обсерватория
@ -22020,7 +21973,6 @@
лицезреть лицезреть
никулин никулин
неукоснительно неукоснительно
нельсон
понапрасну понапрасну
фанатичный фанатичный
модернизм модернизм
@ -22864,7 +22816,6 @@
гадина гадина
провиант провиант
лещ лещ
мекка
флагманский флагманский
адресоваться адресоваться
внешнеторговый внешнеторговый
@ -32494,7 +32445,6 @@
холодить холодить
исцелиться исцелиться
дарвиновский дарвиновский
оКсана
шайтан шайтан
алешин алешин
объёме объёме
@ -32893,7 +32843,6 @@
аркадьевна аркадьевна
овсяный овсяный
обвешать обвешать
мАксвелл
сплетничать сплетничать
узнаваемость узнаваемость
улать улать
@ -32995,7 +32944,6 @@
сомать сомать
пунктуальный пунктуальный
фотокорреспондент фотокорреспондент
бояРин
отвержение отвержение
философствование философствование
дыгать дыгать
@ -38746,7 +38694,6 @@
мет мет
кинохроника кинохроника
подложка подложка
бойфренд
очиститель очиститель
лёгкостью лёгкостью
голубовато голубовато
@ -38934,7 +38881,6 @@
петраков петраков
трип трип
маттиас маттиас
кондрат
пинг пинг
отписка отписка
фукс фукс
@ -40162,7 +40108,6 @@
апример апример
чейни чейни
отшатываться отшатываться
серБия
столыпинский столыпинский
смердеть смердеть
физкультурник физкультурник
@ -40898,7 +40843,6 @@
сворачивание сворачивание
дикобраз дикобраз
родин родин
Макар
переговорщик переговорщик
чметр чметр
валуй валуй
@ -41894,7 +41838,6 @@
просечь просечь
ярик ярик
генуэзский генуэзский
Стеб
глазастый глазастый
рпг рпг
цыганов цыганов
@ -44210,7 +44153,6 @@
шпангоут шпангоут
плебисцит плебисцит
наркомафия наркомафия
Бойфренд
лавчонка лавчонка
джеми джеми
проверенный проверенный
@ -45721,7 +45663,6 @@
стройно стройно
неход неход
несоизмеримый несоизмеримый
Спорткомплекс
усреднение усреднение
конвульсии конвульсии
саров саров
@ -45832,7 +45773,6 @@
авитаминоз авитаминоз
прополоскать прополоскать
порыбачить порыбачить
Понт
топологический топологический
хромовый хромовый
аутотренинг аутотренинг
@ -47174,7 +47114,6 @@
пятикратный пятикратный
интеллектуальность интеллектуальность
пва пва
евроремонт
маканин маканин
окопчик окопчик
твердышев твердышев
@ -54281,7 +54220,6 @@
карканье карканье
пасторский пасторский
виан виан
перегРин
тоить тоить
окунев окунев
юхан юхан
@ -54906,7 +54844,6 @@
измор измор
трый трый
шляхетский шляхетский
бРин
лима лима
нестроевой нестроевой
суннитский суннитский
@ -55758,7 +55695,6 @@
пиноккио пиноккио
идиотический идиотический
ереть ереть
Клава
послепродажный послепродажный
бревна бревна
несомнить несомнить
@ -56484,7 +56420,6 @@
жосслить жосслить
полтонна полтонна
комбо комбо
Метрострой
мелеть мелеть
руга руга
антропософия антропософия
@ -59199,7 +59134,6 @@
зульфия зульфия
фотокросс фотокросс
провинциализм провинциализм
кордицеПС
комендантша комендантша
луковский луковский
аудить аудить
@ -61891,7 +61825,6 @@
хайти хайти
кворт кворт
пупка пупка
Таврия
мирье мирье
исподний исподний
видеопрокат видеопрокат
@ -63203,7 +63136,7 @@
флорентино флорентино
сагитировать сагитировать
долан долан
санкт-Петербург Санкт-Петербург
салаг салаг
засека засека
батюшок батюшок
@ -63405,7 +63338,6 @@
пакля пакля
заартачиться заартачиться
лайдекер лайдекер
гРин
агатовый агатовый
микросекунда микросекунда
брыззать брыззать
@ -63460,7 +63392,6 @@
субара субара
пожестче пожестче
омоним омоним
Евроремонт
амбиз амбиз
йцукен йцукен
долби долби
@ -69811,7 +69742,6 @@
еврисфей еврисфей
браунли браунли
возьаться возьаться
Казанцев
задержки задержки
портфельчик портфельчик
неумолимость неумолимость
@ -74213,7 +74143,6 @@
социобиология социобиология
взбрыкивать взбрыкивать
чёткость чёткость
Экспоцентр
засекречивание засекречивание
бомжик бомжик
хамар хамар
@ -78233,7 +78162,6 @@
войт войт
эндорэ эндорэ
окк окк
Автосервис
бандеровский бандеровский
правдин правдин
обрисовываться обрисовываться
@ -81985,7 +81913,6 @@
цаостров цаостров
гафгарьон гафгарьон
астрорунет астрорунет
мэйРин
промаркировать промаркировать
дегтяренко дегтяренко
четвёрки четвёрки
@ -84423,7 +84350,6 @@
оршанский оршанский
карачевский карачевский
неизменять неизменять
Минздрав
чулка чулка
трехосный трехосный
четать четать

View file

@ -106,20 +106,47 @@ android {
// } // }
} }
task getDictSizes { task validateDictionaries {
inputs.dir fileTree(dir:'assets', excludes:['dict.properties']) inputs.dir fileTree(dir:'assets', excludes:['dict.properties'])
outputs.file "t9build.properties" outputs.file "t9build.properties"
doLast { doLast {
println "Calculating dict size..." String errors = "";
inputs.getFiles().each {File file -> inputs.getFiles().each {File file ->
println "dict: "+ file.name println "Validating dictionary: " + file.name
ant.propertyfile(file:"assets/dict.properties") {
entry(key: "size."+ file.name, value: file.length()) def geographicalName = ~"[A-Z]\\w+-[^\\n]+"
def uniqueWords = [:]
int lineNumber = 0
file.eachLine {line ->
lineNumber++
if (line.matches("\\d")) {
errors += "Dictionary '" + file.name + "' is invalid. Found numbers on line " + lineNumber + ". Please, remove all numbers.\n"
}
if (line.matches("^\\P{L}+\$")) {
errors += "Dictionary '" + file.name + "' is invalid. Found a garbage word: '" + line + "' on line " + lineNumber + ".\n"
}
if (line.matches("^.\$")) {
errors += "Dictionary '" + file.name + "' is invalid. Found a single letter: '" + line + "' on line " + lineNumber + ". Remove all single letters. The alphabet will be added automatically.\n"
}
String uniqueWordKey = line ==~ geographicalName ? line : line.toLowerCase()
if (uniqueWords[uniqueWordKey] != null && uniqueWords[uniqueWordKey] == true) {
errors += "Dictionary '" + file.name + "' is invalid. Found a repeating word: '" + line + "' on line " + lineNumber + ". Ensure all words appear only once.\n"
} else {
uniqueWords[uniqueWordKey] = true
}
} }
} }
if (errors != "") {
throw new GradleException(errors)
}
} }
} }
preBuild.dependsOn getDictSizes preBuild.dependsOn validateDictionaries
preBuild.mustRunAfter getDictSizes preBuild.mustRunAfter validateDictionaries

View file

@ -17,9 +17,11 @@
<string name="pref_loaduserdict">Зареди свой речник</string> <string name="pref_loaduserdict">Зареди свой речник</string>
<string name="pref_truncatedict">Изтрий речник</string> <string name="pref_truncatedict">Изтрий речник</string>
<string name="dictionary_loading">Зареждане на речник…</string> <string name="dictionary_import_bad_char">Неуспешно зареждане. Невалидна дума \"%1$s\" на ред %2$d за език \"%3$s\".</string>
<string name="dictionary_import_error">Несупешно зареждане на речник за език \"%1$s\" (%2$s).</string>
<string name="dictionary_loading">Зареждане на речник (%1$s)…</string>
<string name="dictionary_loading_user_dict">Зареждане на вашия речник…</string> <string name="dictionary_loading_user_dict">Зареждане на вашия речник…</string>
<string name="dictionary_load_title">Зареди речник</string> <string name="dictionary_load_title">Зареждане на речник</string>
<string name="dictionary_not_found">Неуспешно зареждане. Липсва речник за %1$s.</string> <string name="dictionary_not_found">Неуспешно зареждане. Липсва речник за \"%1$s\".</string>
<string name="dictionary_truncated">Речникът е изтрит успешно</string> <string name="dictionary_truncated">Речникът е изтрит успешно</string>
</resources> </resources>

View file

@ -14,10 +14,10 @@
<string name="pref_choose_languages">Sprachen</string> <string name="pref_choose_languages">Sprachen</string>
<string name="pref_loaddict">Wörterbuch laden</string> <string name="pref_loaddict">Wörterbuch laden</string>
<string name="pref_loaduserdict">Benutzerwörterbuch laden</string> <string name="pref_loaduserdict">Benutzerwörterbuch laden</string>
<string name="pref_truncatedict">Wörterbuch löschen</string>
<string name="dictionary_loading">Lade Wörterbuch…</string> <string name="dictionary_loading">Lade Wörterbuch (%1$s)</string>
<string name="dictionary_loading_user_dict">Lade Benutzerwörterbuch…</string> <string name="dictionary_loading_user_dict">Lade Benutzerwörterbuch…</string>
<string name="dictionary_load_title">Wörterbuch laden</string> <string name="dictionary_load_title">Wörterbuch laden</string>
<string name="dictionary_not_found">Wird nicht geladen. Wörterbuch für %1$s nicht gefunden.</string> <string name="dictionary_not_found">Wird nicht geladen. Wörterbuch für \"%1$s\" nicht gefunden.</string>
<string name="pref_truncatedict">Wörterbuch löschen</string>
</resources> </resources>

View file

@ -14,10 +14,11 @@
<string name="pref_choose_languages">Choisir langues</string> <string name="pref_choose_languages">Choisir langues</string>
<string name="pref_loaddict">Charger le dictionnaire</string> <string name="pref_loaddict">Charger le dictionnaire</string>
<string name="pref_loaduserdict">Charger le dictionnaire utilisateur</string> <string name="pref_loaduserdict">Charger le dictionnaire utilisateur</string>
<string name="pref_truncatedict">Supprimer le dictionaire</string>
<string name="dictionary_loading">Chargement du dictionnaire…</string> <string name="dictionary_import_error">Echec du chargement de dictionnaire pour langue «%1$s» (%2$s).</string>
<string name="dictionary_loading">Chargement du dictionnaire (%1$s)…</string>
<string name="dictionary_loading_user_dict">Chargement du dictionnaire utilisateur…</string> <string name="dictionary_loading_user_dict">Chargement du dictionnaire utilisateur…</string>
<string name="dictionary_load_title">Charger le dictionnaire</string> <string name="dictionary_load_title">Charger le dictionnaire</string>
<string name="dictionary_not_found">Echec du chargement. Dictionnaire %1$s introuvable.</string> <string name="dictionary_not_found">Echec du chargement. Dictionnaire «%1$s» introuvable.</string>
<string name="pref_truncatedict">Supprimer le dictionaire</string>
</resources> </resources>

View file

@ -14,11 +14,11 @@
<string name="pref_choose_languages">Le lingue</string> <string name="pref_choose_languages">Le lingue</string>
<string name="pref_loaddict">Carica dizionario</string> <string name="pref_loaddict">Carica dizionario</string>
<string name="pref_loaduserdict">Carica dizionario utente</string> <string name="pref_loaduserdict">Carica dizionario utente</string>
<string name="pref_truncatedict">Eliminare il dizionario</string>
<string name="dictionary_loading">Caricamento dizionario…</string> <string name="dictionary_loading">Caricamento dizionario (%1$s)</string>
<string name="dictionary_loading_user_dict">Caricamento dizionario utente…</string> <string name="dictionary_loading_user_dict">Caricamento dizionario utente…</string>
<string name="dictionary_load_title">Caricamento dizionario</string> <string name="dictionary_load_title">Caricamento dizionario</string>
<string name="dictionary_not_found">Impossibile caricare. Dizionario per %1$s non trovato.</string> <string name="dictionary_not_found">Impossibile caricare. Dizionario per “%1$s” non trovato.</string>
<string name="pref_truncatedict">Eliminare il dizionario</string>
</resources> </resources>

View file

@ -17,9 +17,10 @@
<string name="pref_loaduserdict">Загрузить свой словарь</string> <string name="pref_loaduserdict">Загрузить свой словарь</string>
<string name="pref_truncatedict">Очистить словарь</string> <string name="pref_truncatedict">Очистить словарь</string>
<string name="dictionary_loading">Загрузка словаря…</string> <string name="dictionary_import_error">Ошибка загрузки словаря для языка «%1$s» (%2$s).</string>
<string name="dictionary_loading">Загрузка словаря (%1$s)…</string>
<string name="dictionary_loading_user_dict">Загрузка пользовательского словаря…</string> <string name="dictionary_loading_user_dict">Загрузка пользовательского словаря…</string>
<string name="dictionary_load_title">Загрузить словарь</string> <string name="dictionary_load_title">Загрузить словарь</string>
<string name="dictionary_not_found">Ошибка загрузки. Словарь %1$s не найден.</string> <string name="dictionary_not_found">Ошибка загрузки. Словарь «%1$s» не найден.</string>
<string name="dictionary_truncated">Словарь успешно очищен.</string> <string name="dictionary_truncated">Словарь успешно очищен.</string>
</resources> </resources>

View file

@ -14,10 +14,11 @@
<string name="pref_choose_languages">Вибір мови</string> <string name="pref_choose_languages">Вибір мови</string>
<string name="pref_loaddict">Завантажити словник</string> <string name="pref_loaddict">Завантажити словник</string>
<string name="pref_loaduserdict">Завантажити свій словник</string> <string name="pref_loaduserdict">Завантажити свій словник</string>
<string name="pref_truncatedict">Очистити словник</string>
<string name="dictionary_loading">Завантаження словника…</string> <string name="dictionary_import_error">Помилка завантаження словника для мови «%1$s» (%2$s).</string>
<string name="dictionary_loading">Завантаження словника (%1$s)…</string>
<string name="dictionary_loading_user_dict">Завантаження словника користувача…</string> <string name="dictionary_loading_user_dict">Завантаження словника користувача…</string>
<string name="dictionary_load_title">Завантажити словник</string> <string name="dictionary_load_title">Завантажити словник</string>
<string name="dictionary_not_found">Помилка завантаження. Словник %1$s не знайдено.</string> <string name="dictionary_not_found">Помилка завантаження. Словник «%1$s» не знайдено.</string>
<string name="pref_truncatedict">Очистити словник</string>
</resources> </resources>

View file

@ -21,9 +21,11 @@
<string translatable="false" name="pref_loaduserdictdesc">SDcard/traditionalt9/user.lang.dict (lang: en/ru/de/fr)</string> <string translatable="false" name="pref_loaduserdictdesc">SDcard/traditionalt9/user.lang.dict (lang: en/ru/de/fr)</string>
<string name="pref_truncatedict">Clear dictionary</string> <string name="pref_truncatedict">Clear dictionary</string>
<string name="dictionary_loading">Loading dictionary…</string> <string name="dictionary_import_bad_char">Loading failed. Invalid word \"%1$s\" on line %2$d of language \"%3$s\".</string>
<string name="dictionary_import_error">Failed importing dictionary for language \"%1$s\" (%2$s).</string>
<string name="dictionary_loading">Loading dictionary (%1$s)…</string>
<string name="dictionary_loading_user_dict">Loading user dictionary…</string> <string name="dictionary_loading_user_dict">Loading user dictionary…</string>
<string name="dictionary_load_title">Load dictionary</string> <string name="dictionary_load_title">Load dictionary</string>
<string name="dictionary_not_found">Loading failed. Dictionary for %1$s not found.</string> <string name="dictionary_not_found">Loading failed. Dictionary for \"%1$s\" not found.</string>
<string name="dictionary_truncated">Dictionary successfully cleared.</string> <string name="dictionary_truncated">Dictionary successfully cleared.</string>
</resources> </resources>

View file

@ -2,6 +2,8 @@ const { basename } = require('path');
const { createReadStream, existsSync } = require('fs'); const { createReadStream, existsSync } = require('fs');
const GEO_NAME = /[A-Z]\w+\-[^\n]+/;
function printHelp() { function printHelp() {
console.log(`Usage ${basename(process.argv[1])} LOCALE FILENAME.txt `); console.log(`Usage ${basename(process.argv[1])} LOCALE FILENAME.txt `);
@ -28,17 +30,44 @@ function validateInput() {
function getRegularWordKey(locale, word) {
if (typeof word !== 'string' || word.length === 0) {
return '';
}
return GEO_NAME.test(word) ? word : word.toLocaleLowerCase(locale);
}
function getWordKeyPreservingCaptialization(locale, word, wordMap) {
if (typeof word !== 'string' || word.length === 0 || typeof wordMap !== 'object') {
return '';
}
let wordKey = word.toLocaleLowerCase(locale);
if (GEO_NAME.test(word) || word.toLocaleLowerCase(locale) !== word) {
wordKey = word;
if (wordMap[word.toLocaleLowerCase(locale)]) {
delete wordMap[word.toLocaleLowerCase(locale)];
}
}
return wordKey;
}
async function removeRepeatingWords({ fileName, locale }) { async function removeRepeatingWords({ fileName, locale }) {
const lineReader = require('readline').createInterface({ const lineReader = require('readline').createInterface({
input: createReadStream(fileName) input: createReadStream(fileName)
}); });
const geographicalName = /[A-Z]\w+\-[^\n]+/;
const wordMap = {}; const wordMap = {};
for await (const line of lineReader) { for await (const line of lineReader) {
const wordKey = geographicalName.test(line) ? line : line.toLocaleLowerCase(locale); wordMap[getWordKeyPreservingCaptialization(locale, line, wordMap)] = true;
wordMap[wordKey] = true
} }
return Object.keys(wordMap); return Object.keys(wordMap);
@ -57,4 +86,6 @@ function printWords(wordList) {
/** main **/ /** main **/
removeRepeatingWords(validateInput()).then(words => printWords(words)); removeRepeatingWords(validateInput())
.then(words => printWords(words))
.catch(e => console.error(e));

View file

@ -1,315 +0,0 @@
/* ____________________________________________________________________________
*
* File: UnicodeBOMInputStream.java
* Author: Gregory Pakosz.
* Date: 02 - November - 2005
* ____________________________________________________________________________
*/
package com.stackoverflow.answer;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
/**
* The <code>UnicodeBOMInputStream</code> class wraps any
* <code>InputStream</code> and detects the presence of any Unicode BOM
* (Byte Order Mark) at its beginning, as defined by
* <a href="http://www.faqs.org/rfcs/rfc3629.html">RFC 3629 - UTF-8, a transformation format of ISO 10646</a>
*
* <p>The
* <a href="http://www.unicode.org/unicode/faq/utf_bom.html">Unicode FAQ</a>
* defines 5 types of BOMs:<ul>
* <li><pre>00 00 FE FF = UTF-32, big-endian</pre></li>
* <li><pre>FF FE 00 00 = UTF-32, little-endian</pre></li>
* <li><pre>FE FF = UTF-16, big-endian</pre></li>
* <li><pre>FF FE = UTF-16, little-endian</pre></li>
* <li><pre>EF BB BF = UTF-8</pre></li>
* </ul></p>
*
* <p>Use the {@link #getBOM()} method to know whether a BOM has been detected
* or not.
* </p>
* <p>Use the {@link #skipBOM()} method to remove the detected BOM from the
* wrapped <code>InputStream</code> object.</p>
*/
public class UnicodeBOMInputStream extends InputStream
{
/**
* Type safe enumeration class that describes the different types of Unicode
* BOMs.
*/
public static final class BOM
{
/**
* NONE.
*/
public static final BOM NONE = new BOM(new byte[]{},"NONE");
/**
* UTF-8 BOM (EF BB BF).
*/
public static final BOM UTF_8 = new BOM(new byte[]{(byte)0xEF,
(byte)0xBB,
(byte)0xBF},
"UTF-8");
/**
* UTF-16, little-endian (FF FE).
*/
public static final BOM UTF_16_LE = new BOM(new byte[]{ (byte)0xFF,
(byte)0xFE},
"UTF-16 little-endian");
/**
* UTF-16, big-endian (FE FF).
*/
public static final BOM UTF_16_BE = new BOM(new byte[]{ (byte)0xFE,
(byte)0xFF},
"UTF-16 big-endian");
/**
* UTF-32, little-endian (FF FE 00 00).
*/
public static final BOM UTF_32_LE = new BOM(new byte[]{ (byte)0xFF,
(byte)0xFE,
(byte)0x00,
(byte)0x00},
"UTF-32 little-endian");
/**
* UTF-32, big-endian (00 00 FE FF).
*/
public static final BOM UTF_32_BE = new BOM(new byte[]{ (byte)0x00,
(byte)0x00,
(byte)0xFE,
(byte)0xFF},
"UTF-32 big-endian");
/**
* Returns a <code>String</code> representation of this <code>BOM</code>
* value.
*/
public final String toString()
{
return description;
}
/**
* Returns the bytes corresponding to this <code>BOM</code> value.
*/
public final byte[] getBytes()
{
final int length = bytes.length;
final byte[] result = new byte[length];
// Make a defensive copy
System.arraycopy(bytes,0,result,0,length);
return result;
}
private BOM(final byte bom[], final String description)
{
assert(bom != null) : "invalid BOM: null is not allowed";
assert(description != null) : "invalid description: null is not allowed";
assert(description.length() != 0) : "invalid description: empty string is not allowed";
this.bytes = bom;
this.description = description;
}
final byte bytes[];
private final String description;
} // BOM
/**
* Constructs a new <code>UnicodeBOMInputStream</code> that wraps the
* specified <code>InputStream</code>.
*
* @param inputStream an <code>InputStream</code>.
*
* @throws NullPointerException when <code>inputStream</code> is
* <code>null</code>.
* @throws IOException on reading from the specified <code>InputStream</code>
* when trying to detect the Unicode BOM.
*/
public UnicodeBOMInputStream(final InputStream inputStream) throws NullPointerException,
IOException
{
if (inputStream == null)
throw new NullPointerException("invalid input stream: null is not allowed");
in = new PushbackInputStream(inputStream,4);
final byte bom[] = new byte[4];
final int read = in.read(bom);
switch(read)
{
case 4:
if ((bom[0] == (byte)0xFF) &&
(bom[1] == (byte)0xFE) &&
(bom[2] == (byte)0x00) &&
(bom[3] == (byte)0x00))
{
this.bom = BOM.UTF_32_LE;
break;
}
else
if ((bom[0] == (byte)0x00) &&
(bom[1] == (byte)0x00) &&
(bom[2] == (byte)0xFE) &&
(bom[3] == (byte)0xFF))
{
this.bom = BOM.UTF_32_BE;
break;
}
case 3:
if ((bom[0] == (byte)0xEF) &&
(bom[1] == (byte)0xBB) &&
(bom[2] == (byte)0xBF))
{
this.bom = BOM.UTF_8;
break;
}
case 2:
if ((bom[0] == (byte)0xFF) &&
(bom[1] == (byte)0xFE))
{
this.bom = BOM.UTF_16_LE;
break;
}
else
if ((bom[0] == (byte)0xFE) &&
(bom[1] == (byte)0xFF))
{
this.bom = BOM.UTF_16_BE;
break;
}
default:
this.bom = BOM.NONE;
break;
}
if (read > 0)
in.unread(bom,0,read);
}
/**
* Returns the <code>BOM</code> that was detected in the wrapped
* <code>InputStream</code> object.
*
* @return a <code>BOM</code> value.
*/
public final BOM getBOM()
{
// BOM type is immutable.
return bom;
}
/**
* Skips the <code>BOM</code> that was found in the wrapped
* <code>InputStream</code> object.
*
* @return this <code>UnicodeBOMInputStream</code>.
*
* @throws IOException when trying to skip the BOM from the wrapped
* <code>InputStream</code> object.
*/
public final synchronized UnicodeBOMInputStream skipBOM() throws IOException
{
if (!skipped)
{
in.skip(bom.bytes.length);
skipped = true;
}
return this;
}
/**
* {@inheritDoc}
*/
public int read() throws IOException
{
return in.read();
}
/**
* {@inheritDoc}
*/
public int read(final byte b[]) throws IOException,
NullPointerException
{
return in.read(b,0,b.length);
}
/**
* {@inheritDoc}
*/
public int read(final byte b[],
final int off,
final int len) throws IOException,
NullPointerException
{
return in.read(b,off,len);
}
/**
* {@inheritDoc}
*/
public long skip(final long n) throws IOException
{
return in.skip(n);
}
/**
* {@inheritDoc}
*/
public int available() throws IOException
{
return in.available();
}
/**
* {@inheritDoc}
*/
public void close() throws IOException
{
in.close();
}
/**
* {@inheritDoc}
*/
public synchronized void mark(final int readlimit)
{
in.mark(readlimit);
}
/**
* {@inheritDoc}
*/
public synchronized void reset() throws IOException
{
in.reset();
}
/**
* {@inheritDoc}
*/
public boolean markSupported()
{
return in.markSupported();
}
private final PushbackInputStream in;
private final BOM bom;
private boolean skipped = false;
} // UnicodeBOMInputStream

View file

@ -59,16 +59,8 @@ public class DictionaryDb {
} }
public static void beginTransaction() { public static void runInTransaction(Runnable r) {
getInstance().beginTransaction(); getInstance().runInTransaction(r);
}
public static void endTransaction(boolean success) {
if (success) {
getInstance().setTransactionSuccessful();
}
getInstance().endTransaction();
} }

View file

@ -0,0 +1,7 @@
package io.github.sspanak.tt9.db;
public class DictionaryImportAbortedException extends Exception{
public DictionaryImportAbortedException() {
super("Dictionary import stopped by request.");
}
}

View file

@ -0,0 +1,14 @@
package io.github.sspanak.tt9.db;
public class DictionaryImportException extends Exception {
public final String file;
public final String word;
public final long line;
DictionaryImportException(String file, String word, long line) {
super("Dictionary import failed");
this.file = file;
this.word = word;
this.line = line;
}
}

View file

@ -0,0 +1,259 @@
package io.github.sspanak.tt9.db;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.regex.Pattern;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException;
import io.github.sspanak.tt9.languages.InvalidLanguageException;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.preferences.T9Preferences;
public class DictionaryLoader {
private final AssetManager assets;
private final T9Preferences prefs;
private boolean isStopped = true;
private int currentFile = 0;
private long lastProgressUpdate = 0;
private final Pattern containsPunctuation = Pattern.compile("\\p{Punct}(?<!-)");
public DictionaryLoader(Context context) {
assets = context.getAssets();
prefs = T9Preferences.getInstance();
}
public void load(Handler handler, ArrayList<Language> languages) {
new Thread() {
@Override
public void run() {
currentFile = 0;
isStopped = false;
// SQLite does not support parallel queries, so let's import them one by one
for (Language lang : languages) {
if (isStopped) {
break;
}
importAll(handler, lang);
currentFile++;
}
}
}.start();
}
public void stop() {
isStopped = true;
}
private void importAll(Handler handler, Language language) {
final String logTag = "tt9.DictionaryLoader.importAll";
if (language == null) {
Logger.e(logTag, "Failed loading a dictionary for NULL language.");
sendError(handler, InvalidLanguageException.class.getSimpleName(), -1);
return;
}
DictionaryDb.runInTransaction(() -> {
long start = System.currentTimeMillis();
importLetters(language);
Logger.i(
logTag,
"Loaded letters for '" + language.getName() + "' language in: " + (System.currentTimeMillis() - start) + " ms"
);
try {
start = System.currentTimeMillis();
importWords(handler, language);
Logger.i(
logTag,
"Dictionary: '" + language.getDictionaryFile() + "'" +
" processing time: " + (System.currentTimeMillis() - start) + " ms"
);
} catch (DictionaryImportAbortedException e) {
stop();
Logger.i(
logTag,
e.getMessage() + ". File '" + language.getDictionaryFile() + "' not imported."
);
} catch (DictionaryImportException e) {
stop();
sendImportError(handler, DictionaryImportException.class.getSimpleName(), language.getId(), e.line, e.word);
Logger.e(
logTag,
" Invalid word: '" + e.word
+ "' in dictionary: '" + language.getDictionaryFile() + "'"
+ " on line " + e.line
+ " of language '" + language.getName() + "'. "
+ e.getMessage()
);
} catch (Exception e) {
stop();
sendError(handler, e.getClass().getSimpleName(), language.getId());
Logger.e(
logTag,
"Failed loading dictionary: " + language.getDictionaryFile() +
" for language '" + language.getName() + "'. "
+ e.getMessage()
);
}
});
}
private void importLetters(Language language) {
ArrayList<Word> letters = new ArrayList<>();
for (int key = 0; key <= 9; key++) {
for (String langChar : language.getKeyCharacters(key)) {
if (langChar.length() == 1 && langChar.charAt(0) >= '0' && langChar.charAt(0) <= '9') {
// We do not want 0-9 as "word suggestions" in Predictive mode. It looks confusing
// when trying to type a word and also, one can type them by holding the respective
// key.
continue;
}
Word word = new Word();
word.langId = language.getId();
word.frequency = 0;
word.sequence = String.valueOf(key);
word.word = langChar;
letters.add(word);
}
}
DictionaryDb.insertWordsSync(letters);
}
private void importWords(Handler handler, Language language) throws Exception {
importWords(handler, language, language.getDictionaryFile());
}
private void importWords(Handler handler, Language language, String dictionaryFile) throws Exception {
long totalWords = countWords(dictionaryFile);
BufferedReader br = new BufferedReader(new InputStreamReader(assets.open(dictionaryFile), StandardCharsets.UTF_8));
ArrayList<Word> dbWords = new ArrayList<>();
long line = 0;
sendProgressMessage(handler, language, 0, 0);
for (String word; (word = br.readLine()) != null; line++) {
if (isStopped) {
br.close();
sendProgressMessage(handler, language, 0, 0);
throw new DictionaryImportAbortedException();
}
validateWord(language, word, line);
dbWords.add(stringToWord(language, word));
if (line % prefs.getDictionaryImportWordChunkSize() == 0) {
DictionaryDb.insertWordsSync(dbWords);
dbWords.clear();
}
if (totalWords > 0) {
int progress = (int) Math.floor(100.0 * line / totalWords);
sendProgressMessage(handler, language, progress, prefs.getDictionaryImportProgressUpdateInterval());
}
}
br.close();
sendProgressMessage(handler, language, 100, 0);
}
private long countWords(String filename) {
try (LineNumberReader reader = new LineNumberReader(new InputStreamReader(assets.open(filename), StandardCharsets.UTF_8))) {
//noinspection ResultOfMethodCallIgnored
reader.skip(Long.MAX_VALUE);
long lines = reader.getLineNumber();
reader.close();
return lines;
} catch (Exception e) {
Logger.w("DictionaryLoader.countWords", "Could not count the lines of file: " + filename + ". " + e.getMessage());
return 0;
}
}
private void validateWord(Language language, String word, long line) throws DictionaryImportException {
if (!language.isPunctuationPartOfWords() && containsPunctuation.matcher(word).find()) {
throw new DictionaryImportException(language.getDictionaryFile(), word, line);
}
}
private Word stringToWord(Language language, String word) throws InvalidLanguageCharactersException {
Word dbWord = new Word();
dbWord.langId = language.getId();
dbWord.frequency = 0;
dbWord.sequence = language.getDigitSequenceForWord(word);
dbWord.word = word;
return dbWord;
}
private void sendProgressMessage(Handler handler, Language language, int progress, int progressUpdateInterval) {
long now = System.currentTimeMillis();
if (now - lastProgressUpdate < progressUpdateInterval) {
return;
}
lastProgressUpdate = now;
Bundle bundle = new Bundle();
bundle.putInt("languageId", language.getId());
bundle.putInt("progress", progress);
bundle.putInt("currentFile", currentFile);
Message msg = new Message();
msg.setData(bundle);
handler.sendMessage(msg);
}
private void sendError(Handler handler, String message, int langId) {
Bundle bundle = new Bundle();
bundle.putString("error", message);
bundle.putInt("languageId", langId);
Message msg = new Message();
msg.setData(bundle);
handler.sendMessage(msg);
}
private void sendImportError(Handler handler, String message, int langId, long fileLine, String word) {
Bundle bundle = new Bundle();
bundle.putString("error", message);
bundle.putLong("fileLine", fileLine);
bundle.putInt("languageId", langId);
bundle.putString("word", word);
Message msg = new Message();
msg.setData(bundle);
handler.sendMessage(msg);
}
}

View file

@ -0,0 +1,15 @@
package io.github.sspanak.tt9.languages;
public class InvalidLanguageCharactersException extends Exception {
private Language language;
public InvalidLanguageCharactersException(Language language, String extraMessage) {
super("Some characters are not supported in language: " + language.getName() + ". " + extraMessage);
this.language = language;
}
public Language getLanguage() {
return language;
}
}

View file

@ -70,7 +70,7 @@ public class Language {
return chars; return chars;
} }
public String getDigitSequenceForWord(String word) throws Exception { public String getDigitSequenceForWord(String word) throws InvalidLanguageCharactersException {
StringBuilder sequence = new StringBuilder(); StringBuilder sequence = new StringBuilder();
String lowerCaseWord = word.toLowerCase(locale); String lowerCaseWord = word.toLowerCase(locale);
@ -83,9 +83,7 @@ public class Language {
} }
if (word.length() != sequence.length()) { if (word.length() != sequence.length()) {
throw new Exception( throw new InvalidLanguageCharactersException(this, "Failed generating digit sequence for word: '" + word);
"Failed generating digit sequence for word: '" + word + "'. Some characters are not supported in language: " + name
);
} }
return sequence.toString(); return sequence.toString();

View file

@ -2,10 +2,10 @@ package io.github.sspanak.tt9.preferences;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import androidx.preference.PreferenceManager;
import android.view.KeyEvent; import android.view.KeyEvent;
import androidx.preference.PreferenceManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -23,11 +23,13 @@ public class T9Preferences {
private final SharedPreferences prefs; private final SharedPreferences prefs;
private final SharedPreferences.Editor prefsEditor; private final SharedPreferences.Editor prefsEditor;
public T9Preferences (Context context) { public T9Preferences (Context context) {
prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefsEditor = prefs.edit(); prefsEditor = prefs.edit();
} }
public static T9Preferences getInstance() { public static T9Preferences getInstance() {
if (self == null) { if (self == null) {
self = new T9Preferences(TraditionalT9.getMainContext()); self = new T9Preferences(TraditionalT9.getMainContext());
@ -36,7 +38,8 @@ public class T9Preferences {
return self; return self;
} }
/************* VALIDATORS *************/
/************* validators *************/
private boolean doesLanguageExist(int langId) { private boolean doesLanguageExist(int langId) {
return LanguageCollection.getLanguage(langId) != null; return LanguageCollection.getLanguage(langId) != null;
@ -70,7 +73,7 @@ public class T9Preferences {
} }
/************* PREFERENCES OPERATIONS *************/ /************* input settings *************/
public ArrayList<Integer> getEnabledLanguages() { public ArrayList<Integer> getEnabledLanguages() {
int languageMask = prefs.getInt("pref_enabled_languages", 1); int languageMask = prefs.getInt("pref_enabled_languages", 1);
@ -101,6 +104,7 @@ public class T9Preferences {
prefsEditor.apply(); prefsEditor.apply();
} }
public int getTextCase() { public int getTextCase() {
return prefs.getInt("pref_text_case", InputMode.CASE_LOWER); return prefs.getInt("pref_text_case", InputMode.CASE_LOWER);
} }
@ -131,6 +135,7 @@ public class T9Preferences {
} }
} }
public int getInputMode() { public int getInputMode() {
return prefs.getInt("pref_input_mode", InputMode.MODE_PREDICTIVE); return prefs.getInt("pref_input_mode", InputMode.MODE_PREDICTIVE);
} }
@ -146,18 +151,24 @@ public class T9Preferences {
} }
/************* hotkey settings *************/
public int getKeyBackspace() { public int getKeyBackspace() {
return prefs.getInt("pref_key_backspace", KeyEvent.KEYCODE_BACK); return prefs.getInt("pref_key_backspace", KeyEvent.KEYCODE_BACK);
} }
public int getKeyInputMode() { return prefs.getInt("pref_key_input_mode", KeyEvent.KEYCODE_POUND); } public int getKeyInputMode() { return prefs.getInt("pref_key_input_mode", KeyEvent.KEYCODE_POUND); }
public int getKeyOtherActions() { return prefs.getInt("pref_key_other_actions", KeyEvent.KEYCODE_STAR); } public int getKeyOtherActions() { return prefs.getInt("pref_key_other_actions", KeyEvent.KEYCODE_STAR); }
public int getSuggestionsMin() { return 8; } /************* internal settings *************/
public int getSuggestionsMax() { return 20; }
public int getDictionaryImportProgressUpdateInterval() { return 100; /* ms */ }
public int getDictionaryImportWordChunkSize() { return 1000; /* words */ }
public int getSuggestionsMax() { return 20; }
public int getSuggestionsMin() { return 8; }
/************* add word, last word *************/
public String getLastWord() { public String getLastWord() {
return prefs.getString("last_word", ""); return prefs.getString("last_word", "");
@ -173,5 +184,4 @@ public class T9Preferences {
public void clearLastWord() { public void clearLastWord() {
this.saveLastWord(""); this.saveLastWord("");
} }
} }

View file

@ -1,6 +1,5 @@
package io.github.sspanak.tt9.ui; package io.github.sspanak.tt9.ui;
import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.ListActivity; import android.app.ListActivity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
@ -8,38 +7,24 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.SystemClock;
import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.ListAdapter; import android.widget.ListAdapter;
import android.widget.ListView; import android.widget.ListView;
import com.stackoverflow.answer.UnicodeBOMInputStream;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DictionaryDb; import io.github.sspanak.tt9.db.DictionaryDb;
import io.github.sspanak.tt9.db.Word; import io.github.sspanak.tt9.db.DictionaryImportException;
import io.github.sspanak.tt9.db.DictionaryLoader;
import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException;
import io.github.sspanak.tt9.languages.InvalidLanguageException;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.preferences.T9Preferences; import io.github.sspanak.tt9.preferences.T9Preferences;
@ -49,365 +34,11 @@ import io.github.sspanak.tt9.settings_legacy.SettingAdapter;
public class TraditionalT9Settings extends ListActivity implements DialogInterface.OnCancelListener { public class TraditionalT9Settings extends ListActivity implements DialogInterface.OnCancelListener {
AsyncTask<String, Integer, Reply> task = null; private DictionaryLoader loader;
final static String userdictname = "user.%s.dict"; ProgressDialog progressDialog;
final static String sddir = "tt9";
Context mContext = null; Context mContext = null;
public static class LoadException extends Exception {
private static final long serialVersionUID = 3323913652550046354L;
public LoadException() {
super();
}
}
private static class Reply {
public boolean status;
private final List<String> msgs;
protected Reply() {
this.status = true;
this.msgs = new ArrayList<>(4);
}
protected void addMsg(String msg) throws LoadException {
msgs.add(msg);
if (msgs.size() > 6) {
msgs.add("Too many errors, bailing.");
throw new LoadException();
}
}
protected void forceMsg(String msg) {
msgs.add(msg);
}
}
private void finishAndShowError(ProgressDialog pd, Reply result, int title){
if (pd != null) {
// Logger.d("onPostExecute", "pd");
if (pd.isShowing()) {
pd.dismiss();
}
}
if (result == null) {
// bad thing happened
Logger.e("onPostExecute", "Bad things happen?");
} else {
String msg = TextUtils.join("\n", result.msgs);
Logger.d("onPostExecute", "Result: " + result.status + " " + msg);
if (!result.status) {
showErrorDialog(getResources().getString(title), msg);
}
}
}
private static void closeStream(Closeable is, Reply reply) {
if (is == null) {
return;
}
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
reply.forceMsg("Couldn't close stream: " + e.getMessage());
}
}
private class LoadDictTask extends AsyncTask<String, Integer, Reply> {
/**
* The system calls this to perform work in a worker thread and delivers
* it the parameters given to AsyncTask.execute()
*/
ProgressDialog pd;
long size;
long pos;
boolean internal;
String[] dicts;
ArrayList<Language> mSupportedLanguages;
LoadDictTask(int msgid, boolean intern, ArrayList<Language> supportedLanguages) {
internal = intern;
dicts = new String[supportedLanguages.size()];
int x = 0;
for (Language language : supportedLanguages) {
if (intern) {
dicts[x++] = language.getDictionaryFile();
} else {
dicts[x++] = String.format(userdictname, language.getName().toLowerCase(Locale.ENGLISH));
}
}
mSupportedLanguages = supportedLanguages;
pd = new ProgressDialog(TraditionalT9Settings.this);
pd.setMessage(getResources().getString(msgid));
pd.setOnCancelListener(TraditionalT9Settings.this);
}
private long getDictSizes(boolean internal, String[] dicts) {
if (internal) {
InputStream input;
Properties props = new Properties();
try {
input = getAssets().open("dict.properties");
props.load(input);
long total = 0;
for (String dict : dicts) {
total += Long.parseLong(props.getProperty("size." + dict));
}
return total;
} catch (IOException e) {
Logger.e("getDictSizes", "Unable to get dict sizes");
e.printStackTrace();
return -1;
} catch (NumberFormatException e) {
Logger.e("getDictSizes", "Unable to parse sizes");
return -1;
}
} else {
File backupfile = new File(Environment.getExternalStorageDirectory(), sddir);
long total = 0;
File f;
for (String dict : dicts) {
f = new File(backupfile, dict);
if (f.exists() && f.isFile()) {
total = total + f.length();
} else {
total = total + 0;
}
}
return total;
}
}
@Override protected void onPreExecute() {
size = getDictSizes(internal, dicts);
pos = 0;
if ( size >= 0 ) {
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pd.setMax(10000);
} else {
pd.setProgressStyle(ProgressDialog.STYLE_SPINNER);
}
pd.show();
}
@Override
protected Reply doInBackground(String... mode) {
Reply reply = new Reply();
long startnow, endnow;
startnow = SystemClock.uptimeMillis();
// add characters first, then dictionary:
Logger.d("doInBackground", "Adding characters...");
processChars(mSupportedLanguages);
Logger.d("doInBackground", "Characters added.");
Logger.d("doInBackground", "Adding dict(s)...");
InputStream dictstream = null;
try {
for (int x=0; x<dicts.length; x++) {
if (internal) {
try {
dictstream = getAssets().open(dicts[x]);
reply = processFile(dictstream, reply, mSupportedLanguages.get(x), dicts[x]);
} catch (IOException e) {
e.printStackTrace();
reply.status = false;
reply.forceMsg("IO Error: " + e.getMessage());
}
} else {
try {
dictstream = new FileInputStream(new File(
new File(Environment.getExternalStorageDirectory(), sddir), dicts[x]));
reply = processFile(dictstream, reply, mSupportedLanguages.get(x), dicts[x]);
} catch (FileNotFoundException e) {
reply.status = false;
reply.forceMsg("File not found: " + e.getMessage());
final String msg = mContext.getString(R.string.dictionary_not_found, dicts[x]);
//Logger.d("T9Setting.load", "Built string. Calling Toast.");
((Activity) mContext).runOnUiThread(new Runnable() {
@Override
public void run() {
UI.toast(mContext, msg);
}
});
closeStream(dictstream, reply); // this is silly but it
// stops IDE nagging at me.
} catch (IOException e) {
reply.status = false;
reply.forceMsg("IO Error: " + e.getMessage());
closeStream(dictstream, reply); // this is silly but it
return reply; // stops IDE nagging at me.
}
}
closeStream(dictstream, reply);
}
} catch (LoadException e) {
// too many errors, bail
closeStream(dictstream, reply);
}
endnow = SystemClock.uptimeMillis();
Logger.d("TIMING", "Execution time: " + (endnow - startnow) + " ms");
return reply;
}
/**
* processChars
* Inserts single characters.
*/
private void processChars(List<Language> allLanguages) {
ArrayList<Word> list = new ArrayList<>();
try {
for (Language lang : allLanguages) {
for (int key = 0; key <= 9; key++) {
for (String langChar : lang.getKeyCharacters(key)) {
if (langChar.length() == 1 && langChar.charAt(0) >= '0' && langChar.charAt(0) <= '9') {
// We do not want 0-9 as "word suggestions" in Predictive mode. It looks confusing
// when trying to type a word and also, one can type them by holding the respective
// key.
continue;
}
Word word = new Word();
word.langId = lang.getId();
word.sequence = String.valueOf(key);
word.word = langChar;
word.frequency = 0;
list.add(word);
}
}
}
DictionaryDb.insertWordsSync(list);
} catch (Exception e) {
Logger.e("processChars", e.getMessage());
}
}
private String getLine(BufferedReader br, Reply rpl, String fname) throws LoadException {
try {
return br.readLine();
} catch (IOException e) {
e.printStackTrace();
rpl.status = false;
rpl.addMsg("IO Error ("+fname+"): " + e.getMessage());
}
return null;
}
private Reply processFile(InputStream is, Reply rpl, Language lang, String fname)
throws LoadException, IOException {
long start = System.currentTimeMillis();
UnicodeBOMInputStream ubis = new UnicodeBOMInputStream(is);
BufferedReader br = new BufferedReader(new InputStreamReader(ubis));
ubis.skipBOM();
int freq;
String seq;
int linecount = 1;
int wordlen;
String fileWord = getLine(br, rpl, fname);
ArrayList<Word> dbWords = new ArrayList<>();
int insertChunkSize = 1000;
int progressUpdateInterval = 100; // ms
long lastProgressUpdate = 0;
try {
DictionaryDb.beginTransaction();
while (fileWord != null) {
if (isCancelled()) {
rpl.status = false;
rpl.addMsg("User cancelled.");
break;
}
if (fileWord.contains(" ")) {
rpl.status = false;
rpl.addMsg("Cannot parse word with spaces: " + fileWord);
break;
}
freq = 0;
wordlen = fileWord.getBytes(StandardCharsets.UTF_8).length;
pos += wordlen;
// replace junk characters:
fileWord = fileWord.replace("\uFEFF", "");
try {
seq = lang.getDigitSequenceForWord(fileWord);
} catch (Exception e) {
rpl.status = false;
rpl.addMsg("Error on word ("+fileWord+") line "+
linecount+" in (" + fname+"): "+
getResources().getString(R.string.add_word_badchar, lang.getName(), fileWord));
break;
}
linecount++;
Word word = new Word();
word.sequence = seq;
word.langId = lang.getId();
word.word = fileWord;
word.frequency = freq;
dbWords.add(word);
if (linecount % insertChunkSize == 0) {
DictionaryDb.insertWordsSync(dbWords);
dbWords.clear();
}
if (size >= 0 && System.currentTimeMillis() - lastProgressUpdate > progressUpdateInterval) {
publishProgress((int) ((float) pos / size * 10000));
lastProgressUpdate = System.currentTimeMillis();
}
fileWord = getLine(br, rpl, fname);
}
DictionaryDb.insertWordsSync(dbWords);
DictionaryDb.endTransaction(true);
dbWords.clear();
publishProgress((int) ((float) pos / size * 10000));
} catch (Exception e) {
DictionaryDb.endTransaction(false);
Logger.e("processFile", e.getMessage());
} finally {
br.close();
is.close();
ubis.close();
is.close();
Logger.d("processFile", "Inserted: " + fname + " in: " + (System.currentTimeMillis() - start) + "ms");
}
return rpl;
}
@Override
protected void onProgressUpdate(Integer... progress) {
if (pd.isShowing()) {
pd.setProgress(progress[0]);
}
}
@Override
protected void onPostExecute(Reply result) {
finishAndShowError(pd, result, R.string.dictionary_load_title);
}
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -433,22 +64,33 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
} }
@Override
public void onCancel(DialogInterface dint) {
if (loader != null) {
loader.stop();
}
}
@Override @Override
protected void onListItemClick(ListView l, View v, int position, long id) { protected void onListItemClick(ListView l, View v, int position, long id) {
Setting s = (Setting)getListView().getItemAtPosition(position); Setting s = (Setting)getListView().getItemAtPosition(position);
if (s.id.equals("help")) switch (s.id) {
openHelp(); case "help":
else if (s.id.equals("loaddict")) openHelp();
preloader(R.string.dictionary_loading, true); break;
else if (s.id.equals("truncatedict")) { case "loaddict":
truncateWords(); loadDictionaries();
break;
case "truncatedict":
truncateWords();
break;
default:
s.clicked(mContext);
break;
} }
else if (s.id.equals("loaduserdict"))
preloader(R.string.dictionary_loading_user_dict, false);
else
s.clicked(mContext);
} }
private void openHelp() { private void openHelp() {
Intent i = new Intent(Intent.ACTION_VIEW); Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(getString(R.string.help_url))); i.setData(Uri.parse(getString(R.string.help_url)));
@ -465,50 +107,106 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
DictionaryDb.truncateWords(afterTruncate); DictionaryDb.truncateWords(afterTruncate);
} }
private void loadDictionaries() {
ArrayList<Language> languages = LanguageCollection.getAll(T9Preferences.getInstance().getEnabledLanguages());
initProgress(100 * languages.size());
private void preloader(int msgid, boolean internal) { Handler loadHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
String error = msg.getData().getString("error", null);
if (error != null) {
hideProgress();
handleError(
error,
msg.getData().getInt("languageId", -1),
msg.getData().getLong("fileLine", -1),
msg.getData().getString("word", "")
);
} else {
int langId = msg.getData().getInt("languageId", -1);
Language lang = LanguageCollection.getLanguage(langId);
String langName = lang != null ? lang.getName() : "???";
task = new LoadDictTask( String title = getResources().getString(R.string.dictionary_loading, langName);
msgid, showProgress(
internal, msg.getData().getInt("currentFile", 0),
LanguageCollection.getAll(T9Preferences.getInstance().getEnabledLanguages()) msg.getData().getInt("progress", 0),
); title
task.execute(); );
}
}
};
loader = new DictionaryLoader(this);
loader.load(loadHandler, languages);
} }
private void initProgress(int max) {
if (progressDialog == null) {
progressDialog = new ProgressDialog(this);
progressDialog.setOnCancelListener(TraditionalT9Settings.this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
}
progressDialog.setMax(max);
}
private void showProgress(int currentFile, int currentFileProgress, String title) {
if (progressDialog == null) {
return;
}
if (title != null) {
progressDialog.setMessage(title);
}
int totalProgress = 100 * currentFile + currentFileProgress;
if (totalProgress <= 0 || totalProgress >= progressDialog.getMax()) {
progressDialog.dismiss();
} else {
progressDialog.setProgress(totalProgress);
if (!progressDialog.isShowing()) {
progressDialog.show();
}
}
}
private void hideProgress() {
if (progressDialog != null) {
progressDialog.dismiss();
}
}
private void handleError(String errorType, int langId, long line, String word) {
Language lang = LanguageCollection.getLanguage(langId);
String message;
if (lang == null || errorType.equals(InvalidLanguageException.class.getSimpleName())) {
message = getString(R.string.add_word_invalid_language);
} else if (errorType.equals(DictionaryImportException.class.getSimpleName()) || errorType.equals(InvalidLanguageCharactersException.class.getSimpleName())) {
String languageName = lang.getName();
message = getString(R.string.dictionary_import_bad_char, word, line, languageName);
} else if (errorType.equals(IOException.class.getSimpleName()) || errorType.equals(FileNotFoundException.class.getSimpleName())) {
String languageName = lang.getName();
message = getString(R.string.dictionary_not_found, languageName);
} else {
String languageName = lang.getName();
message = getString(R.string.dictionary_import_error, languageName, errorType);
}
showErrorDialog(getString(R.string.dictionary_load_title), message);
}
private void showErrorDialog(CharSequence title, CharSequence msg) { private void showErrorDialog(CharSequence title, CharSequence msg) {
showErrorDialog(new AlertDialog.Builder(this), title, msg); AlertDialog.Builder builder = new AlertDialog.Builder(this);
} builder
.setMessage(msg)
private void showErrorDialog(AlertDialog.Builder builder, CharSequence title, CharSequence msg) { .setTitle(title)
builder.setMessage(msg).setTitle(title) .setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.dismiss());
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
AlertDialog dialog = builder.create(); AlertDialog dialog = builder.create();
dialog.show(); dialog.show();
} }
private void showErrorDialogID(AlertDialog.Builder builder, int titleid, int msgid) {
builder.setMessage(msgid).setTitle(titleid)
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
@Override
public void onCancel(DialogInterface dint) {
task.cancel(false);
}
} }