ru.pocketbyte.locolaser.google.resource.GoogleSheetResources.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of resource-googlesheet Show documentation
Show all versions of resource-googlesheet Show documentation
Implementation of source for LocoLaser tool to work with Google Sheets.
/*
* Copyright © 2017 Denis Shurygin. All rights reserved.
* Licensed under the Apache License, Version 2.0
*/
package ru.pocketbyte.locolaser.google.resource
import com.google.api.services.sheets.v4.Sheets
import com.google.api.services.sheets.v4.model.BatchUpdateValuesRequest
import com.google.api.services.sheets.v4.model.ValueRange
import ru.pocketbyte.locolaser.config.ExtraParams
import ru.pocketbyte.locolaser.resource.BaseTableResources
import ru.pocketbyte.locolaser.resource.Resources
import ru.pocketbyte.locolaser.entity.Quantity
import ru.pocketbyte.locolaser.google.GoogleSheetResourcesConfig
import ru.pocketbyte.locolaser.resource.entity.ResMap
import ru.pocketbyte.locolaser.resource.formatting.FormattingType
import ru.pocketbyte.locolaser.utils.LogUtils
import java.io.File
import java.io.IOException
/**
* @author Denis Shurygin
*/
class GoogleSheetResources(
private val sourceConfig: GoogleSheetResourcesConfig,
private val service: Sheets,
override val formattingType: FormattingType
) : BaseTableResources() {
companion object {
const val IGNORE_INDEX = "-"
fun valueToSourceValue(value: String): String {
// Add "'" if string beginning from "'", "+" or "="
return value.replace("^(['+=])".toRegex(), "'$1")
}
fun sourceValueToValue(sourceValue: String): String {
// Remove "'" from the beginning
return sourceValue.replace("^(')".toRegex(), "")
}
}
private var mIgnoreRows: List? = null
private lateinit var mColumnIndexes: ColumnIndexes
private var mTitleRow = -1
private var mQuery: List>? = null
override fun allFiles(locales: Set): List {
return emptyList()
}
override fun write(resMap: ResMap, extraParams: ExtraParams?) {
if (fetchCellsIfNeeded()) {
var totalRows = rowsCount
val batchRequest = mutableListOf()
val newRows = ArrayList>()
for ((locale, resLocale) in resMap) {
val localeColumn = mColumnIndexes.indexesMap[locale]
if (localeColumn != null && localeColumn >= 0) {
for ((_, resItem) in resLocale) {
for (i in resItem.values.indices) {
val resValue = formattingType.convert(resItem.values[i])
val resRow = getRow(resItem.key, resValue.quantity)
// =====================================
// Prepare batch for found missed resMap
if (resRow != null) {
if (getValue(localeColumn, resRow)?.isBlank() != false) {
batchRequest.add(
ValueRange()
.setRange("${columnName(localeColumn)}$resRow")
.setValues(listOf(listOf(valueToSourceValue(resValue.value))))
)
val comment = resValue.comment
if (comment != null && mColumnIndexes.comment >= 0) {
batchRequest.add(
ValueRange()
.setRange("${columnName(mColumnIndexes.comment)}$resRow")
.setValues(listOf(listOf(valueToSourceValue(comment))))
)
}
}
} else {
// =====================================
// Reserve rows for new keys
if (getRow(resItem.key, resValue.quantity) == null) {
registerRowForKey(++totalRows, resItem.key, resValue.quantity)
newRows.add(Pair(resItem.key, resValue.quantity))
}
}
}
}
}
}
service.spreadsheets()
.values()
.batchUpdate(
sourceConfig.id,
BatchUpdateValuesRequest()
.setValueInputOption("RAW")
.setData(batchRequest)
)
.execute()
.let {
LogUtils.info("${it.totalUpdatedCells} cells updated.")
}
if (totalRows > rowsCount) {
val newKeysList = mutableListOf()
val newValuesBatch = mutableListOf()
for ((newRowKey, newRowQuantity) in newRows) {
getRow(newRowKey, newRowQuantity)?.let { row ->
// Key Cell
newKeysList.add(newRowKey)
val resItem = resMap[Resources.BASE_LOCALE]?.get(newRowKey)
// Quantity Cell
if (mColumnIndexes.quantity >= 0) {
if (resItem?.isHasQuantities != false) {
newValuesBatch.add(
ValueRange()
.setRange("${columnName(mColumnIndexes.quantity)}$row")
.setValues(listOf(listOf(newRowQuantity.toString())))
)
}
}
// Comment Cell
val comment = resItem?.valueForQuantity(newRowQuantity)?.comment
if (comment != null && mColumnIndexes.comment >= 0) {
newValuesBatch.add(
ValueRange()
.setRange("${columnName(mColumnIndexes.comment)}$row")
.setValues(listOf(listOf(valueToSourceValue(comment))))
)
}
// Value Cells
for ((locale, resLocale) in resMap) {
val localeColumn = mColumnIndexes.indexesMap[locale]
if (localeColumn != null && localeColumn >= 0) {
resLocale[newRowKey]?.valueForQuantity(newRowQuantity)?.let {
val resValue = formattingType.convert(it)
newValuesBatch.add(
ValueRange()
.setRange("${columnName(localeColumn)}$row")
.setValues(listOf(listOf(valueToSourceValue(resValue.value))))
)
}
}
}
} ?: { // getRow() equal null
//TODO warn
}()
}
try {
val firstNewRow = newRows[0].let {
getRow(it.first, it.second)
}
// Appending keys
service.spreadsheets().values()
.append(
sourceConfig.id,
"${columnName(mColumnIndexes.key)}${firstNewRow}",
ValueRange().setValues(listOf(newKeysList)).setMajorDimension("COLUMNS")
)
.setValueInputOption("RAW")
.execute()
.let {
LogUtils.info("${it.updates.updatedCells} new rows inserted.")
}
// Updating values
service.spreadsheets()
.values()
.batchUpdate(
sourceConfig.id,
BatchUpdateValuesRequest()
.setValueInputOption("RAW")
.setData(newValuesBatch)
)
.execute()
.let {
LogUtils.info("${it.totalUpdatedCells} cells updated.")
}
} catch (e: IOException) {
e.printStackTrace()
throw RuntimeException("ERROR: Failed to write sheet. Sheet Id: " + sourceConfig.id!!)
}
// Invalidate Query
mQuery = null
}
}
}
override val firstRow: Int
get() {
fetchCellsIfNeeded()
return mTitleRow + 1
}
override val columnIndexes: ColumnIndexes
get() {
fetchCellsIfNeeded()
return mColumnIndexes
}
override fun getValue(col: Int, row: Int): String? {
fetchCellsIfNeeded()
if (mIgnoreRows == null || mIgnoreRows!!.contains(row))
return null
return mQuery
?.getOrNull(row - mTitleRow - 1)
?.getOrNull(col - columnIndexes.min)
?.toString()
}
override val rowsCount: Int
get() {
fetchCellsIfNeeded()
return (mQuery?.size ?: 0) + firstRow - 1
}
override fun valueToSourceValue(value: String): String {
return Companion.valueToSourceValue(value)
}
override fun sourceValueToValue(sourceValue: String): String {
return Companion.sourceValueToValue(sourceValue)
}
private fun fetchCellsIfNeeded(): Boolean {
if (mQuery == null) {
val workSheet = sourceConfig.worksheetTitle?.let { "$it!" } ?: ""
// Ignore rows
val ignoreRows = ArrayList()
val indexColumnFeed: ValueRange?
try {
indexColumnFeed = service.spreadsheets().values()
.get(sourceConfig.id, "${workSheet}A:A")
.execute()
} catch (e: IOException) {
e.printStackTrace()
return false
}
indexColumnFeed?.getValues()?.forEachIndexed { row, rowValues ->
if (IGNORE_INDEX == rowValues.getOrElse(0) { "" }) {
ignoreRows.add(row + 1)
}
}
mIgnoreRows = ignoreRows
// Title row
var titleRow = 1
while (mIgnoreRows!!.contains(titleRow))
titleRow++
mTitleRow = titleRow
LogUtils.info("Title row: $mTitleRow")
val titleRowFeed: ValueRange?
try {
titleRowFeed = service.spreadsheets().values()
.get(sourceConfig.id, "${workSheet}$titleRow:$titleRow")
.execute()
} catch (e: IOException) {
e.printStackTrace()
return false
}
val indexesMap = titleRowFeed?.toIndexesMap()
val key = indexesMap?.get(sourceConfig.keyColumn)
if (key == null) {
LogUtils.warn("Column " + sourceConfig.keyColumn + " not found.")
return false
}
val quantity = indexesMap[sourceConfig.quantityColumn] ?: -1
if (quantity == -1 && sourceConfig.quantityColumn != null) {
LogUtils.warn("Column ${sourceConfig.quantityColumn} not found.")
}
val comment = indexesMap[sourceConfig.commentColumn] ?: -1
if (comment == -1 && sourceConfig.commentColumn != null) {
LogUtils.warn("Column ${sourceConfig.commentColumn} not found.")
}
val metadata = indexesMap[sourceConfig.metadataColumn] ?: -1
if (comment == -1 && sourceConfig.metadataColumn != null) {
LogUtils.warn("Column ${sourceConfig.metadataColumn} not found.")
}
mColumnIndexes = ColumnIndexes(key, quantity, comment, metadata, indexesMap)
LogUtils.info("Column Indexes: $mColumnIndexes")
try {
val minColumn = columnName(mColumnIndexes.min)
val maxColumn = columnName(mColumnIndexes.max)
mQuery = service.spreadsheets().values()
.get(sourceConfig.id, "${workSheet}$minColumn${titleRow + 1}:$maxColumn")
.execute()
.getValues()
} catch (e: IOException) {
e.printStackTrace()
}
}
return mQuery != null
}
private fun ValueRange.toIndexesMap(): Map {
val result = mutableMapOf()
getValues().getOrNull(0)?.forEachIndexed { index, value ->
result[value.toString()] = index + 1
}
return result
}
private fun columnName(column: Int): String {
return if (column < 26) {
(64 + column).toChar().toString()
} else {
columnName(column / 26) + (64 + column % 26).toChar()
}
}
}