1360 add custom publishing code for a custom app

This commit is contained in:
Sean Mac Gillicuddy 2019-11-14 14:39:52 +00:00
parent ac4717a79e
commit 8057ef6b6e
11 changed files with 271 additions and 67 deletions

View File

@ -2,6 +2,7 @@ import plugin.KiwixConfigurationPlugin
plugins {
id("com.android.application")
id("com.github.triplet.play") version("2.5.0")
}
apply plugin: KiwixConfigurationPlugin
@ -67,6 +68,10 @@ android {
}
play {
enabled = true
serviceAccountCredentials = file("../google.json")
track = "alpha"
releaseStatus = "draft"
resolutionStrategy = "fail"
}

View File

@ -15,7 +15,8 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.50")
implementation("com.dicedmelon.gradle:jacoco-android:0.1.4")
implementation("org.jlleitschuh.gradle:ktlint-gradle:8.2.0")
implementation("com.github.triplet.gradle:play-publisher:2.5.0")
implementation("com.google.apis:google-api-services-androidpublisher:v3-rev129-1.25.0")
implementation(gradleApi())
implementation(localGroovy())
}

View File

@ -0,0 +1,122 @@
/* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package custom
import com.android.build.gradle.api.ApkVariantOutput
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport
import com.google.api.client.http.FileContent
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.json.GenericJson
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.services.androidpublisher.AndroidPublisher
import com.google.api.services.androidpublisher.AndroidPublisherScopes
import com.google.api.services.androidpublisher.model.ExpansionFile
import com.google.api.services.androidpublisher.model.ExpansionFilesUploadResponse
import com.google.api.services.androidpublisher.model.Track
import com.google.api.services.androidpublisher.model.TrackRelease
import java.io.File
import java.io.FileInputStream
import java.security.KeyStore
fun createPublisher(auth: File): AndroidPublisher {
val transport = buildTransport()
val factory = JacksonFactory.getDefaultInstance()
val credential =
GoogleCredential.fromStream(auth.inputStream(), transport, factory)
.createScoped(listOf(AndroidPublisherScopes.ANDROIDPUBLISHER))
return AndroidPublisher.Builder(transport, JacksonFactory.getDefaultInstance()) {
credential.initialize(it.setReadTimeout(0))
}.setApplicationName("kiwixcustom").build()
}
private fun buildTransport(): NetHttpTransport {
val trustStore: String? = System.getProperty("javax.net.ssl.trustStore", null)
val trustStorePassword: String? =
System.getProperty("javax.net.ssl.trustStorePassword", null)
return if (trustStore == null) {
GoogleNetHttpTransport.newTrustedTransport()
} else {
val ks = KeyStore.getInstance(KeyStore.getDefaultType())
FileInputStream(trustStore).use { fis ->
ks.load(fis, trustStorePassword?.toCharArray())
}
NetHttpTransport.Builder().trustCertificates(ks).build()
}
}
class Transaction(
private val publisher: AndroidPublisher,
private val packageName: String,
val editId: String
) {
fun uploadExpansionTo(
file: File,
apkVariantOutput: ApkVariantOutput
): ExpansionFilesUploadResponse = publisher.edits().expansionfiles()
.upload(
packageName,
editId,
apkVariantOutput.versionCodeOverride,
"main",
FileContent("application/octet-stream", file)
).execute().prettyPrint()
fun attachExpansionTo(expansionCode: Int, apkVariantOutput: ApkVariantOutput): ExpansionFile =
publisher.edits().expansionfiles().update(
packageName,
editId,
apkVariantOutput.versionCodeOverride,
"main",
ExpansionFile().apply { referencesVersion = expansionCode }
).execute().prettyPrint()
fun uploadApk(apkVariantOutput: ApkVariantOutput) {
publisher.edits().apks().upload(
packageName,
editId,
FileContent("application/octet-stream", apkVariantOutput.outputFile)
).execute().prettyPrint()
}
fun addToTrackInDraft(apkVariants: List<ApkVariantOutput>): Track =
publisher.edits().tracks().update(packageName, editId, "alpha", Track().apply {
releases = listOf(TrackRelease().apply {
status = "draft"
name = apkVariants[0].versionNameOverride
versionCodes = apkVariants.map { it.versionCodeOverride.toLong() }
})
track = "alpha"
}).execute().prettyPrint()
}
fun AndroidPublisher.transactionWithCommit(packageName: String, func: Transaction.() -> Unit) {
Transaction(
this,
packageName,
edits().insert(packageName, null).execute().prettyPrint().id
).apply {
func()
edits().validate(packageName, editId).execute().prettyPrint()
}.also { edits().commit(packageName, it.editId).execute().prettyPrint() }
}
private fun <T : GenericJson> T.prettyPrint() = also { println(it.toPrettyString()) }

View File

@ -22,7 +22,6 @@ import Libs
import com.android.build.VariantOutput
import com.android.build.gradle.AppExtension
import com.android.build.gradle.api.ApkVariantOutput
import com.github.triplet.gradle.play.PlayPublisherExtension
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.exclude
@ -37,7 +36,7 @@ class AppConfigurer {
}
signingConfigs {
create("releaseSigningConfig") {
this.storeFile = File(target.rootDir, "kiwix-android.keystore")
storeFile = File(target.rootDir, "kiwix-android.keystore")
storePassword = System.getenv("KEY_STORE_PASSWORD") ?: "000000"
keyAlias = System.getenv("KEY_ALIAS") ?: "keystore"
keyPassword = System.getenv("KEY_PASSWORD") ?: "000000"
@ -76,13 +75,6 @@ class AppConfigurer {
cruncherEnabled = true
}
}
target.plugins.apply("com.github.triplet.play")
target.configureExtension<PlayPublisherExtension> {
isEnabled = true
serviceAccountCredentials = File(target.rootDir, "google.json")
track = "alpha"
releaseStatus = "draft"
}
configureDependencies(target)
}

View File

@ -27,7 +27,6 @@ import org.kiwix.kiwixmobile.core.downloader.DownloadRequester
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.core.entity.MetaLinkNetworkEntity
import org.kiwix.kiwixmobile.core.dao.entities.FetchDownloadEntity
import org.kiwix.kiwixmobile.core.dao.entities.FetchDownloadEntity_
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
@ -79,7 +78,7 @@ class FetchDownloadDao @Inject constructor(
}
fun addIfDoesNotExist(
metaLinkNetworkEntity: MetaLinkNetworkEntity,
url: String,
book: Book,
downloadRequester: DownloadRequester
) {
@ -88,7 +87,7 @@ class FetchDownloadDao @Inject constructor(
insert(
downloadRequester.enqueue(
DownloadRequest(
metaLinkNetworkEntity,
url,
book
)
),

View File

@ -18,10 +18,12 @@
package org.kiwix.kiwixmobile.core.downloader
import io.reactivex.Observable
import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import javax.inject.Inject
class DownloaderImpl @Inject constructor(
@ -31,7 +33,7 @@ class DownloaderImpl @Inject constructor(
) : Downloader {
override fun download(book: LibraryNetworkEntity.Book) {
kiwixService.getMetaLinks(book.url)
urlProvider(book)
.take(1)
.subscribe(
{
@ -41,6 +43,10 @@ class DownloaderImpl @Inject constructor(
)
}
private fun urlProvider(book: Book): Observable<String> =
if (book.url.endsWith("meta4")) kiwixService.getMetaLinks(book.url).map { it.relevantUrl.value }
else Observable.just(book.url)
override fun cancelDownload(downloadItem: DownloadItem) {
downloadRequester.cancel(downloadItem)
}

View File

@ -18,8 +18,7 @@
package org.kiwix.kiwixmobile.core.downloader.model
import android.net.Uri
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.core.entity.MetaLinkNetworkEntity
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.utils.StorageUtils
@ -31,14 +30,7 @@ data class DownloadRequest(
val uri: Uri get() = Uri.parse(urlString)
constructor(
metaLinkNetworkEntity: MetaLinkNetworkEntity,
book: LibraryNetworkEntity.Book
) : this(
metaLinkNetworkEntity.relevantUrl.value,
book.title,
book.description
)
constructor(url: String, book: Book) : this(url, book.title, book.description)
fun getDestination(sharedPreferenceUtil: SharedPreferenceUtil): String =
"${sharedPreferenceUtil.prefStorage}/Kiwix/${

View File

@ -1089,10 +1089,7 @@ public abstract class CoreMainActivity extends BaseActivity implements WebViewCa
switch (requestCode) {
case REQUEST_STORAGE_PERMISSION: {
if (hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
finish();
Intent newZimFile = Intents.internal(CoreMainActivity.class);
newZimFile.setData(Uri.fromFile(file));
startActivity(newZimFile);
openZimFile(file);
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(this, dialogStyle());
builder.setMessage(getResources().getString(R.string.reboot_message));

View File

@ -1,5 +1,13 @@
import com.android.build.gradle.api.ApkVariantOutput
import com.android.build.gradle.api.ApplicationVariant
import com.android.build.gradle.internal.dsl.ProductFlavor
import custom.createPublisher
import custom.transactionWithCommit
import plugin.KiwixConfigurationPlugin
import java.net.URI
import java.net.URL
import java.text.SimpleDateFormat
import java.util.Date
plugins {
android
@ -12,20 +20,128 @@ android {
}
flavorDimensions("default")
productFlavors {
// productFlavors {
// create("customexample") {
// versionName = "2017-07"
// versionCode = 1
// applicationIdSuffix = ".kiwixcustomexample"
// configureStrings("Test Custom App")
// }
// }
create(
CustomFlavor(
flavorName = "customexample",
versionName = "2017-07",
url = "http://download.kiwix.org/zim/wikipedia_fr_test.zim",
enforcedLanguage = "en",
appName = "Test Custom App"
),
CustomFlavor(
flavorName = "wikimed",
versionName = "2018-08",
url = "http://download.kiwix.org/zim/wikipedia_en_medicine_novid.zim",
enforcedLanguage = "en",
appName = "Medical Wikipedia"
)
)
all {
val zimFile = File("$projectDir/src", "$name/$name.zim")
createDownloadTask(zimFile)
createPublishApkWithExpansionTask(name, zimFile, applicationVariants)
}
}
splits {
abi {
isUniversalApk = false
}
}
}
apply(from = File("dynamic_flavors.gradle"))
// apply(from = File("dynamic_flavors.gradle"))
fun ProductFlavor.configureStrings(appName: String) {
resValue("string", "app_name", appName)
resValue("string", "app_search_string", "Search $appName")
}
fun ProductFlavor.fetchUrl(): String {
val urlConnection =
URI.create(buildConfigFields["ZIM_URL"]!!.value.replace("\"", "")).toURL()
.openConnection()
urlConnection.connect()
urlConnection.getInputStream()
return urlConnection
.getHeaderField("Location")
?.replace("https", "http")
?: urlConnection.url.toString()
}
fun NamedDomainObjectContainer<ProductFlavor>.create(vararg customFlavors: CustomFlavor) {
customFlavors.forEach { customFlavor ->
create(customFlavor.flavorName) {
versionName = customFlavor.versionName
versionCode = customFlavor.versionCode
applicationIdSuffix = ".kiwixcustom${customFlavor.flavorName}"
buildConfigField("String", "ZIM_URL", "\"${customFlavor.url}\"")
buildConfigField("String", "ENFORCED_LANG", "\"${customFlavor.enforcedLanguage}\"")
configureStrings(customFlavor.appName)
}
}
}
data class CustomFlavor(
val flavorName: String,
val versionName: String,
val versionCode: Int = Date().let {
SimpleDateFormat("YYDDD0").format(it).toInt()
},
val url: String,
val enforcedLanguage: String,
val appName: String
)
fun ProductFlavor.createDownloadTask(file: File): Task {
return tasks.create("download${name.capitalize()}Zim") {
group = "Downloading"
doLast {
if (!file.exists()) {
file.createNewFile()
URL(fetchUrl()).openStream().use {
it.copyTo(file.outputStream())
}
}
}
}
}
fun ProductFlavor.createPublishApkWithExpansionTask(
flavorName: String,
file: File,
applicationVariants: DomainObjectSet<ApplicationVariant>
): Task {
return tasks.create("publish${flavorName.capitalize()}ReleaseApkWithExpansionFile") {
group = "publishing"
description = "Uploads ${flavorName.capitalize()} to the Play Console with an Expansion file"
doLast {
val packageName = "org.kiwix$applicationIdSuffix"
println("packageName $packageName")
val apkVariants = getApkVariants(applicationVariants, flavorName)
createPublisher(File(rootDir, "google.json"))
.transactionWithCommit(packageName) {
apkVariants.forEach(::uploadApk)
uploadExpansionTo(file, apkVariants[0])
apkVariants.drop(1).forEach {
attachExpansionTo(apkVariants[0].versionCodeOverride, it)
}
addToTrackInDraft(apkVariants)
}
}
}
}
afterEvaluate {
tasks.filter { it.name.contains("ReleaseApkWithExpansionFile") }.forEach {
val flavorName =
it.name.substringAfter("publish").substringBefore("ReleaseApkWithExpansionFile")
it.dependsOn.add(tasks.getByName("download${flavorName}Zim"))
it.dependsOn.add(tasks.getByName("assemble${flavorName}Release"))
}
}
fun getApkVariants(applicationVariants: DomainObjectSet<ApplicationVariant>, flavorName: String) =
applicationVariants.find {
it.name.contains("release", true) && it.name.contains(flavorName, true)
}!!.outputs.filterIsInstance<ApkVariantOutput>().sortedBy { it.versionCodeOverride }

View File

@ -1,15 +0,0 @@
android {
def f = file(buildscript.sourceFile.parent + "/wikipedia_fr_test_2017-07.zim");
if (!f.exists()) {
URLConnection urlConnection = new URL('http://download.kiwix.org/zim/wikipedia_fr_test.zim').
openConnection()
urlConnection.connect()
urlConnection.getInputStream()
String url = urlConnection.getURL().toString()
if (urlConnection.getHeaderField("Location") != null) {
url = urlConnection.getHeaderField("Location")
url = url.replace("https", "http")
}
new URL(url).withInputStream { i -> f.withOutputStream { it << i } }
}
}

View File

@ -18,11 +18,9 @@
package org.kiwix.kiwixmobile.custom.main
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.Menu
import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.start
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
@ -34,7 +32,6 @@ import org.kiwix.kiwixmobile.custom.customActivityComponent
import org.kiwix.kiwixmobile.custom.download.CustomDownloadActivity
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasBothFiles
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasFile
import java.io.File
import java.util.Locale
import javax.inject.Inject
@ -57,7 +54,7 @@ class CustomMainActivity : CoreMainActivity() {
super.onCreate(savedInstanceState)
requireEnforcedLanguage()
customFileValidator.validate(
{
onFilesFound = {
when (it) {
is HasFile -> openZimFile(it.file)
is HasBothFiles -> {
@ -66,7 +63,7 @@ class CustomMainActivity : CoreMainActivity() {
}
}
},
{
onNoFilesFound = {
finish()
start<CustomDownloadActivity>()
}
@ -90,7 +87,7 @@ class CustomMainActivity : CoreMainActivity() {
if (BuildConfig.ENFORCED_LANG.isNotEmpty() && BuildConfig.ENFORCED_LANG != currentLocaleCode) {
LanguageUtils.handleLocaleChange(this, BuildConfig.ENFORCED_LANG)
sharedPreferenceUtil.putPrefLanguage(BuildConfig.ENFORCED_LANG)
startActivity(Intent(this, this.javaClass))
recreate()
return true
}
return false
@ -99,12 +96,4 @@ class CustomMainActivity : CoreMainActivity() {
override fun manageZimFiles(tab: Int) {
TODO("not implemented")
}
companion object {
private fun getExpansionAPKFileName() =
"main.${BuildConfig.CONTENT_VERSION_CODE}.${CoreApp.getInstance().packageName}.obb"
fun generateExpansionFilePath(fileName: String = getExpansionAPKFileName()) =
"${CoreApp.getInstance().obbDir}${File.separator}$fileName"
}
}