Linting Fixes AZ

This commit is contained in:
Jobobby04
2020-05-02 00:46:24 -04:00
parent 03e5c5ca10
commit 7e99a9f789
108 changed files with 2962 additions and 2412 deletions
+18 -17
View File
@@ -35,31 +35,32 @@ fun parseHumanReadableByteCount(arg0: String): Double? {
return null
}
fun String?.nullIfBlank(): String? = if (isNullOrBlank())
fun String?.nullIfBlank(): String? = if (isNullOrBlank()) {
null
else
} else {
this
}
fun <K, V> Set<Map.Entry<K, V>>.forEach(action: (K, V) -> Unit) {
forEach { action(it.key, it.value) }
}
val ONGOING_SUFFIX = arrayOf(
"[ongoing]",
"(ongoing)",
"{ongoing}",
"<ongoing>",
"ongoing",
"[incomplete]",
"(incomplete)",
"{incomplete}",
"<incomplete>",
"incomplete",
"[wip]",
"(wip)",
"{wip}",
"<wip>",
"wip"
"[ongoing]",
"(ongoing)",
"{ongoing}",
"<ongoing>",
"ongoing",
"[incomplete]",
"(incomplete)",
"{incomplete}",
"<incomplete>",
"incomplete",
"[wip]",
"(wip)",
"{wip}",
"<wip>",
"wip"
)
val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)
@@ -50,10 +50,11 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
thumbnailUrl?.let { manga.thumbnail_url = it }
// No title bug?
val titleObj = if (Injekt.get<PreferencesHelper>().useJapaneseTitle().getOrDefault())
val titleObj = if (Injekt.get<PreferencesHelper>().useJapaneseTitle().getOrDefault()) {
altTitle ?: title
else
} else {
title
}
titleObj?.let { manga.title = it }
// Set artist (if we can find one)
@@ -102,8 +103,8 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
val tagsDesc = tagsToDescription()
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank)
.joinToString(separator = "\n")
.filter(String::isNotBlank)
.joinToString(separator = "\n")
}
companion object {
@@ -117,24 +118,25 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
private const val EH_ARTIST_NAMESPACE = "artist"
private fun splitGalleryUrl(url: String) =
url.let {
// Only parse URL if is full URL
val pathSegments = if (it.startsWith("http"))
Uri.parse(it).pathSegments
else
it.split('/')
pathSegments.filterNot(String::isNullOrBlank)
}
url.let {
// Only parse URL if is full URL
val pathSegments = if (it.startsWith("http")) {
Uri.parse(it).pathSegments
} else {
it.split('/')
}
pathSegments.filterNot(String::isNullOrBlank)
}
fun galleryId(url: String) = splitGalleryUrl(url)[1]
fun galleryToken(url: String) =
splitGalleryUrl(url)[2]
splitGalleryUrl(url)[2]
fun normalizeUrl(url: String) =
idAndTokenToUrl(galleryId(url), galleryToken(url))
idAndTokenToUrl(galleryId(url), galleryToken(url))
fun idAndTokenToUrl(id: String, token: String) =
"/g/$id/$token/?nw=always"
"/g/$id/$token/?nw=always"
}
}
@@ -32,8 +32,8 @@ class EightMusesSearchMetadata : RaisedSearchMetadata() {
val tagsDesc = tagsToDescription()
manga.description = listOf(titleDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank)
.joinToString(separator = "\n")
.filter(String::isNotBlank)
.joinToString(separator = "\n")
}
companion object {
@@ -34,8 +34,8 @@ class HBrowseSearchMetadata : RaisedSearchMetadata() {
val tagsDesc = tagsToDescription()
manga.description = listOf(titleDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank)
.joinToString(separator = "\n")
.filter(String::isNotBlank)
.joinToString(separator = "\n")
}
companion object {
@@ -31,15 +31,15 @@ class HentaiCafeSearchMetadata : RaisedSearchMetadata() {
manga.status = SManga.UNKNOWN
val detailsDesc = "Title: $title\n" +
"Artist: $artist\n"
"Artist: $artist\n"
val tagsDesc = tagsToDescription()
manga.genre = tagsToGenreString()
manga.description = listOf(detailsDesc, tagsDesc.toString())
.filter(String::isNotBlank)
.joinToString(separator = "\n")
.filter(String::isNotBlank)
.joinToString(separator = "\n")
}
companion object {
@@ -50,6 +50,6 @@ class HentaiCafeSearchMetadata : RaisedSearchMetadata() {
const val BASE_URL = "https://hentai.cafe"
fun hcIdFromUrl(url: String) =
url.split("/").last { it.isNotBlank() }
url.split("/").last { it.isNotBlank() }
}
}
@@ -62,11 +62,13 @@ class HitomiSearchMetadata : RaisedSearchMetadata() {
detailsDesc += "Language: ${it.capitalize()}\n"
}
if (series.isNotEmpty())
if (series.isNotEmpty()) {
detailsDesc += "Series: ${series.joinToString()}\n"
}
if (characters.isNotEmpty())
if (characters.isNotEmpty()) {
detailsDesc += "Characters: ${characters.joinToString()}\n"
}
uploadDate?.let {
detailsDesc += "Upload date: ${EX_DATE_FORMAT.format(Date(it))}\n"
@@ -80,8 +82,8 @@ class HitomiSearchMetadata : RaisedSearchMetadata() {
val tagsDesc = tagsToDescription()
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank)
.joinToString(separator = "\n")
.filter(String::isNotBlank)
.joinToString(separator = "\n")
}
companion object {
@@ -93,9 +95,9 @@ class HitomiSearchMetadata : RaisedSearchMetadata() {
const val BASE_URL = "https://hitomi.la"
fun hlIdFromUrl(url: String) =
url.split('/').last().split('-').last().substringBeforeLast('.')
url.split('/').last().split('-').last().substringBeforeLast('.')
fun urlFromHlId(id: String) =
"$BASE_URL/galleries/$id.html"
"$BASE_URL/galleries/$id.html"
}
}
@@ -44,9 +44,11 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
if (mediaId != null) {
val hqThumbs = Injekt.get<PreferencesHelper>().eh_nh_useHighQualityThumbs().getOrDefault()
typeToExtension(if (hqThumbs) coverImageType else thumbnailImageType)?.let {
manga.thumbnail_url = "https://t.nhentai.net/galleries/$mediaId/${if (hqThumbs)
manga.thumbnail_url = "https://t.nhentai.net/galleries/$mediaId/${if (hqThumbs) {
"cover"
else "thumb"}.$it"
} else {
"thumb"
}}.$it"
}
}
@@ -91,8 +93,8 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
val tagsDesc = tagsToDescription()
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank)
.joinToString(separator = "\n")
.filter(String::isNotBlank)
.joinToString(separator = "\n")
}
companion object {
@@ -108,14 +110,14 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
private const val NHENTAI_CATEGORIES_NAMESPACE = "category"
fun typeToExtension(t: String?) =
when (t) {
"p" -> "png"
"j" -> "jpg"
else -> null
}
when (t) {
"p" -> "png"
"j" -> "jpg"
else -> null
}
fun nhUrlToId(url: String) =
url.split("/").last { it.isNotBlank() }.toLong()
url.split("/").last { it.isNotBlank() }.toLong()
fun nhIdToPath(id: Long) = "/g/$id/"
}
@@ -41,11 +41,12 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
manga.title = it
titleDesc += "Title: $it\n"
}
if (altTitles.isNotEmpty())
if (altTitles.isNotEmpty()) {
titleDesc += "Alternate Titles: \n" + altTitles
.joinToString(separator = "\n", postfix = "\n") {
"$it"
}
.joinToString(separator = "\n", postfix = "\n") {
"$it"
}
}
val detailsDesc = StringBuilder()
artist?.let {
@@ -76,8 +77,8 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
val tagsDesc = tagsToDescription()
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank)
.joinToString(separator = "\n")
.filter(String::isNotBlank)
.joinToString(separator = "\n")
}
companion object {
@@ -87,9 +88,9 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
const val TAG_TYPE_DEFAULT = 0
private fun splitGalleryUrl(url: String) =
url.let {
Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
}
url.let {
Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
}
fun pvIdFromUrl(url: String) = splitGalleryUrl(url).last()
}
@@ -102,7 +103,7 @@ enum class PervEdenLang(val id: Long) {
companion object {
fun source(id: Long) =
values().find { it.id == id }
values().find { it.id == id }
?: throw IllegalArgumentException("Unknown source ID: $id!")
}
}
@@ -55,8 +55,8 @@ class PururinSearchMetadata : RaisedSearchMetadata() {
val tagsDesc = tagsToDescription()
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank)
.joinToString(separator = "\n")
.filter(String::isNotBlank)
.joinToString(separator = "\n")
}
companion object {
@@ -65,8 +65,8 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() {
val tagsDesc = tagsToDescription()
manga.description = listOf(titleDesc, detailsDesc.toString(), tagsDesc.toString())
.filter(String::isNotBlank)
.joinToString(separator = "\n")
.filter(String::isNotBlank)
.joinToString(separator = "\n")
}
companion object {
@@ -77,7 +77,7 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() {
val BASE_URL = "https://www.tsumino.com"
fun tmIdFromUrl(url: String) =
Uri.parse(url).lastPathSegment
Uri.parse(url).lastPathSegment
fun mangaUrlFromId(id: String) = "/Book/Info/$id"
@@ -18,9 +18,9 @@ data class FlatMetadata(
fun <T : RaisedSearchMetadata> raise(clazz: KClass<T>) =
RaisedSearchMetadata.raiseFlattenGson
.fromJson(metadata.extra, clazz.java).apply {
fillBaseFields(this@FlatMetadata)
}
.fromJson(metadata.extra, clazz.java).apply {
fillBaseFields(this@FlatMetadata)
}
}
fun DatabaseHelper.getFlatMetadataForManga(mangaId: Long): PreparedOperation<FlatMetadata?> {
@@ -36,29 +36,29 @@ abstract class RaisedSearchMetadata {
abstract fun copyTo(manga: SManga)
fun tagsToGenreString() =
tags.filter { it.type != TAG_TYPE_VIRTUAL }
tags.filter { it.type != TAG_TYPE_VIRTUAL }
.joinToString { (if (it.namespace != null) "${it.namespace}: " else "") + it.name }
fun tagsToDescription() =
StringBuilder("Tags:\n").apply {
// BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags'
val groupedTags = tags.filter { it.type != TAG_TYPE_VIRTUAL }.groupBy {
it.namespace
}.entries
StringBuilder("Tags:\n").apply {
// BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags'
val groupedTags = tags.filter { it.type != TAG_TYPE_VIRTUAL }.groupBy {
it.namespace
}.entries
groupedTags.forEach { namespace, tags ->
if (tags.isNotEmpty()) {
val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" })
if (namespace != null) {
this += ""
this += namespace
this += ": "
groupedTags.forEach { namespace, tags ->
if (tags.isNotEmpty()) {
val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" })
if (namespace != null) {
this += ""
this += namespace
this += ": "
}
this += joinedTags
this += "\n"
}
this += joinedTags
this += "\n"
}
}
}
fun List<RaisedTag>.ofNamespace(ns: String): List<RaisedTag> {
return filter { it.namespace == ns }
@@ -76,23 +76,23 @@ abstract class RaisedSearchMetadata {
indexedExtra,
0
),
tags.map {
SearchTag(
null,
mangaId,
it.namespace,
it.name,
it.type
)
},
titles.map {
SearchTitle(
null,
mangaId,
it.title,
it.type
)
}
tags.map {
SearchTag(
null,
mangaId,
it.namespace,
it.name,
it.type
)
},
titles.map {
SearchTitle(
null,
mangaId,
it.title,
it.type
)
}
)
}
@@ -126,7 +126,7 @@ abstract class RaisedSearchMetadata {
* @return the property value.
*/
override fun getValue(thisRef: RaisedSearchMetadata, property: KProperty<*>) =
thisRef.getTitleOfType(type)
thisRef.getTitleOfType(type)
/**
* Sets the value of the property for the given object.
@@ -135,7 +135,7 @@ abstract class RaisedSearchMetadata {
* @param value the value to set.
*/
override fun setValue(thisRef: RaisedSearchMetadata, property: KProperty<*>, value: String?) =
thisRef.replaceTitleOfType(type, value)
thisRef.replaceTitleOfType(type, value)
}
}
}
@@ -18,22 +18,22 @@ import exh.metadata.sql.tables.SearchTagTable.COL_TYPE
import exh.metadata.sql.tables.SearchTagTable.TABLE
class SearchTagTypeMapping : SQLiteTypeMapping<SearchTag>(
SearchTagPutResolver(),
SearchTagGetResolver(),
SearchTagDeleteResolver()
SearchTagPutResolver(),
SearchTagGetResolver(),
SearchTagDeleteResolver()
)
class SearchTagPutResolver : DefaultPutResolver<SearchTag>() {
override fun mapToInsertQuery(obj: SearchTag) = InsertQuery.builder()
.table(TABLE)
.build()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: SearchTag) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: SearchTag) = ContentValues(5).apply {
put(COL_ID, obj.id)
@@ -47,19 +47,19 @@ class SearchTagPutResolver : DefaultPutResolver<SearchTag>() {
class SearchTagGetResolver : DefaultGetResolver<SearchTag>() {
override fun mapFromCursor(cursor: Cursor): SearchTag = SearchTag(
id = cursor.getLong(cursor.getColumnIndex(COL_ID)),
mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)),
namespace = cursor.getString(cursor.getColumnIndex(COL_NAMESPACE)),
name = cursor.getString(cursor.getColumnIndex(COL_NAME)),
type = cursor.getInt(cursor.getColumnIndex(COL_TYPE))
id = cursor.getLong(cursor.getColumnIndex(COL_ID)),
mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)),
namespace = cursor.getString(cursor.getColumnIndex(COL_NAMESPACE)),
name = cursor.getString(cursor.getColumnIndex(COL_NAME)),
type = cursor.getInt(cursor.getColumnIndex(COL_TYPE))
)
}
class SearchTagDeleteResolver : DefaultDeleteResolver<SearchTag>() {
override fun mapToDeleteQuery(obj: SearchTag) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}
@@ -17,22 +17,22 @@ import exh.metadata.sql.tables.SearchTitleTable.COL_TYPE
import exh.metadata.sql.tables.SearchTitleTable.TABLE
class SearchTitleTypeMapping : SQLiteTypeMapping<SearchTitle>(
SearchTitlePutResolver(),
SearchTitleGetResolver(),
SearchTitleDeleteResolver()
SearchTitlePutResolver(),
SearchTitleGetResolver(),
SearchTitleDeleteResolver()
)
class SearchTitlePutResolver : DefaultPutResolver<SearchTitle>() {
override fun mapToInsertQuery(obj: SearchTitle) = InsertQuery.builder()
.table(TABLE)
.build()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: SearchTitle) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: SearchTitle) = ContentValues(4).apply {
put(COL_ID, obj.id)
@@ -45,18 +45,18 @@ class SearchTitlePutResolver : DefaultPutResolver<SearchTitle>() {
class SearchTitleGetResolver : DefaultGetResolver<SearchTitle>() {
override fun mapFromCursor(cursor: Cursor): SearchTitle = SearchTitle(
id = cursor.getLong(cursor.getColumnIndex(COL_ID)),
mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)),
title = cursor.getString(cursor.getColumnIndex(COL_TITLE)),
type = cursor.getInt(cursor.getColumnIndex(COL_TYPE))
id = cursor.getLong(cursor.getColumnIndex(COL_ID)),
mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)),
title = cursor.getString(cursor.getColumnIndex(COL_TITLE)),
type = cursor.getInt(cursor.getColumnIndex(COL_TYPE))
)
}
class SearchTitleDeleteResolver : DefaultDeleteResolver<SearchTitle>() {
override fun mapToDeleteQuery(obj: SearchTitle) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}
@@ -1,18 +1,18 @@
package exh.metadata.sql.models
data class SearchTag(
// Tag identifier, unique
// Tag identifier, unique
val id: Long?,
// Metadata this tag is attached to
// Metadata this tag is attached to
val mangaId: Long,
// Tag namespace
// Tag namespace
val namespace: String?,
// Tag name
// Tag name
val name: String,
// Tag type
// Tag type
val type: Int
)
@@ -1,15 +1,15 @@
package exh.metadata.sql.models
data class SearchTitle(
// Title identifier, unique
// Title identifier, unique
val id: Long?,
// Metadata this title is attached to
// Metadata this title is attached to
val mangaId: Long,
// Title
// Title
val title: String,
// Title type, useful for distinguishing between main/alt titles
// Title type, useful for distinguishing between main/alt titles
val type: Int
)
@@ -9,21 +9,25 @@ import exh.metadata.sql.tables.SearchTagTable
interface SearchTagQueries : DbProvider {
fun getSearchTagsForManga(mangaId: Long) = db.get()
.listOfObjects(SearchTag::class.java)
.withQuery(Query.builder()
.table(SearchTagTable.TABLE)
.where("${SearchTagTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build())
.prepare()
.listOfObjects(SearchTag::class.java)
.withQuery(
Query.builder()
.table(SearchTagTable.TABLE)
.where("${SearchTagTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build()
)
.prepare()
fun deleteSearchTagsForManga(mangaId: Long) = db.delete()
.byQuery(DeleteQuery.builder()
.table(SearchTagTable.TABLE)
.where("${SearchTagTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build())
.prepare()
.byQuery(
DeleteQuery.builder()
.table(SearchTagTable.TABLE)
.where("${SearchTagTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build()
)
.prepare()
fun insertSearchTag(searchTag: SearchTag) = db.put().`object`(searchTag).prepare()
@@ -31,10 +35,12 @@ interface SearchTagQueries : DbProvider {
fun deleteSearchTag(searchTag: SearchTag) = db.delete().`object`(searchTag).prepare()
fun deleteAllSearchTags() = db.delete().byQuery(DeleteQuery.builder()
fun deleteAllSearchTags() = db.delete().byQuery(
DeleteQuery.builder()
.table(SearchTagTable.TABLE)
.build())
.prepare()
.build()
)
.prepare()
fun setSearchTagsForManga(mangaId: Long, tags: List<SearchTag>) {
db.inTransaction {
@@ -9,21 +9,25 @@ import exh.metadata.sql.tables.SearchTitleTable
interface SearchTitleQueries : DbProvider {
fun getSearchTitlesForManga(mangaId: Long) = db.get()
.listOfObjects(SearchTitle::class.java)
.withQuery(Query.builder()
.table(SearchTitleTable.TABLE)
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build())
.prepare()
.listOfObjects(SearchTitle::class.java)
.withQuery(
Query.builder()
.table(SearchTitleTable.TABLE)
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build()
)
.prepare()
fun deleteSearchTitlesForManga(mangaId: Long) = db.delete()
.byQuery(DeleteQuery.builder()
.table(SearchTitleTable.TABLE)
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build())
.prepare()
.byQuery(
DeleteQuery.builder()
.table(SearchTitleTable.TABLE)
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build()
)
.prepare()
fun insertSearchTitle(searchTitle: SearchTitle) = db.put().`object`(searchTitle).prepare()
@@ -31,10 +35,12 @@ interface SearchTitleQueries : DbProvider {
fun deleteSearchTitle(searchTitle: SearchTitle) = db.delete().`object`(searchTitle).prepare()
fun deleteAllSearchTitle() = db.delete().byQuery(DeleteQuery.builder()
fun deleteAllSearchTitle() = db.delete().byQuery(
DeleteQuery.builder()
.table(SearchTitleTable.TABLE)
.build())
.prepare()
.build()
)
.prepare()
fun setSearchTitlesForManga(mangaId: Long, titles: List<SearchTitle>) {
db.inTransaction {
@@ -17,7 +17,8 @@ object SearchMetadataTable {
// Insane foreign, primary key to avoid touch manga table
val createTableQuery: String
get() = """CREATE TABLE $TABLE(
get() =
"""CREATE TABLE $TABLE(
$COL_MANGA_ID INTEGER NOT NULL PRIMARY KEY,
$COL_UPLOADER TEXT,
$COL_EXTRA TEXT NOT NULL,
@@ -16,7 +16,8 @@ object SearchTagTable {
const val COL_TYPE = "type"
val createTableQuery: String
get() = """CREATE TABLE $TABLE(
get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_MANGA_ID INTEGER NOT NULL,
$COL_NAMESPACE TEXT,
@@ -14,7 +14,8 @@ object SearchTitleTable {
const val COL_TYPE = "type"
val createTableQuery: String
get() = """CREATE TABLE $TABLE(
get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_MANGA_ID INTEGER NOT NULL,
$COL_TITLE TEXT NOT NULL,