Skip to content

Commit

Permalink
bump to 30, rework libraries & asset extraction
Browse files Browse the repository at this point in the history
  • Loading branch information
pazos committed Dec 26, 2020
1 parent 9099a38 commit 0198bea
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 173 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ project.properties
*.iml

bin
libs
build
app/.*
app/build
Expand Down
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ android {
sourceSets {
main {
assets.srcDirs = [ '../assets' ]
jniLibs.srcDirs = [ '../libs' ]
}
}

Expand Down
10 changes: 6 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@
<!-- common android permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />

<!-- These are used on API30+. Uncomment them when bumping targetSdk
<!-- These are used on API30+ -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-->

<application
android:name=".MainApp"
Expand All @@ -30,6 +31,7 @@
android:vmSafeMode="true"
android:allowBackup="false"
android:fullBackupContent="false"
android:extractNativeLibs="true"
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true" >
<meta-data android:name="android.max_aspect" android:value="3.1" />
Expand Down
265 changes: 98 additions & 167 deletions app/src/main/java/org/koreader/launcher/Assets.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,189 +8,129 @@ import android.view.Gravity
import android.view.ViewGroup
import android.widget.ProgressBar
import androidx.core.content.ContextCompat
import org.koreader.launcher.utils.FileUtils
import java.io.*

class Assets {

companion object {
private const val TAG = "AssetsHelper"
private const val CONTAINER = "7z"
private const val BUNDLE = "module"
private const val MAP = "${BUNDLE}/map.txt"
private const val VERSION = "${BUNDLE}/version.txt"
private const val INSTALLED_VERSION = "git-rev"
}

init {
System.loadLibrary(CONTAINER)
System.loadLibrary("7z")
}

fun extract(activity: Activity): Boolean {
val output = activity.filesDir.absolutePath
return try {
// check if the app has zipped assets
val payload = getFromAsset(activity)
if (payload != null) {
var ok = true
Logger.i("Check file in asset module: $payload")
// upgrade or downgrade files from zip
if (!isSameVersion(activity, payload)) {
showProgress(activity) // show progress dialog (animated dots)
val startTime = System.nanoTime()
Logger.i("Installing new package to $output")
ok = uncompress(activity, "module/$payload", output)
val endTime = System.nanoTime()
val elapsedTime = endTime - startTime
Logger.i("update installed in ${elapsedTime/1000000} milliseconds")
dismissProgress(activity) // dismiss progress dialog
}
if (!ok) {
false
} else {
copyLibs(activity)
}
} else {
// check if the app has other, non-zipped, raw assets
Logger.i("Zip file not found, trying raw assets...")
copyRawAssets(activity)
}
} catch (e: IOException) {
Logger.e(TAG, "error extracting assets:\n$e")
dismissProgress(activity)
false
return if (isNewBundle(activity)) {
val startTime = System.nanoTime()
val result = bootstrap(activity)
val elapsedTime = System.nanoTime() - startTime
Logger.i("update installed in ${elapsedTime / 1000000} milliseconds")
result
} else {
true
}
}

private external fun extract(assetManager: AssetManager, payload: String, output: String): Int

private fun uncompress(activity: Activity, payload: String, output: String): Boolean {
private fun isNewBundle(context: Context): Boolean {
val path = "${context.filesDir.absolutePath}/$INSTALLED_VERSION"
return try {
(extract(activity.assets, payload, output) == 0)
} catch (e: Exception) {
Logger.w(TAG, "error extracting: %e")
false
}
}

private fun copyLibs(context: Context): Boolean {
val assetManager = context.assets
val libsDir = File(context.filesDir.absolutePath + "/libs")
if (!libsDir.exists()) {
libsDir.mkdir()
}

val libsPath = libsDir.absolutePath
try {
val assets = assetManager.list("libs")
return if (assets != null) {
for (asset in assets) {
val file = File(libsPath, asset)
val input = assetManager.open("libs/$asset")
val output = FileOutputStream(file)
copyFile(input, output)
input.close()
output.flush()
output.close()
}
true
} else {
Logger.i("No libraries to copy")
true
}
} catch (e: IOException) {
Logger.e(TAG, "error copying libraries: $e")
return false
}
}

/* copy raw assets from the assets module */
private fun copyRawAssets(context: Context): Boolean {
val assetManager = context.assets
val assetsDir = context.filesDir.absolutePath
var entryPoint = false
try {
val assets = assetManager.list("module")
if (assets != null) {
for (asset in assets) {
val file = File(assetsDir, asset)
val input = assetManager.open("module/$asset")
val output = FileOutputStream(file)
copyFile(input, output)
input.close()
output.flush()
output.close()
// llapp_main.lua is the entry point for frontend code.
if ("llapp_main.lua" == asset) {
entryPoint = true
}
}
if (!File(path).exists()) {
Logger.i("New install")
return true
}
} catch (e: IOException) {
entryPoint = false
Logger.e(TAG, "error copying raw assets: $e")
}
return entryPoint
}

/* get the first compressed file inside the assets module */
private fun getFromAsset(context: Context): String? {
val assetManager = context.assets
try {
val assets = assetManager.list("module")
if (assets != null) {
for (asset in assets) {
if (asset.endsWith(CONTAINER)) {
return asset
}
context.assets.open(VERSION).bufferedReader().use {
val bundledVersion = it.readLine()
val fileReader = FileReader(File(path).absolutePath)
val bufferedReader = BufferedReader(fileReader)
val installedVersion = bufferedReader.readLine()
bufferedReader.close()
return if (bundledVersion == installedVersion) {
Logger.i("Skip installation for revision $bundledVersion")
false
} else {
Logger.i("Found new package revision $bundledVersion")
true
}
}
return null
} catch (e: Exception) {
Logger.e(TAG, "error finding a $CONTAINER in assets store: $e")
return null
Logger.i("New install")
true
}
}

/* check if installed files have the same revision as assets */
private fun isSameVersion(context: Context, file: String): Boolean {
val newVersion = getPackageRevision(file)
try {
val output = context.filesDir.absolutePath
val fileReader = FileReader("$output/git-rev")
val bufferedReader = BufferedReader(fileReader)
val installedVersion = bufferedReader.readLine()
bufferedReader.close()
return if (newVersion == installedVersion) {
Logger.i("Skip installation for revision $newVersion")
true
} else {
Logger.i("Found new package revision $newVersion")
false
}
} catch (e: Exception) {
Logger.i("Found new package revision $newVersion")
return false
}
}

/* get package revision from zipFile name. Zips must use the scheme: name-revision.zip */
private fun getPackageRevision(file: String): String {
val suffix = String.format(".%s", CONTAINER)
val name = file.replace(suffix, "")
val parts = name.split("-".toRegex()).dropLastWhile{ it.isEmpty() }.toTypedArray()
return name.replace(parts[0] + "-", "")
}

/* copy files from stream */
@Throws(IOException::class)
private fun copyFile(input: InputStream, output: OutputStream) {
try {
input.use { source ->
output.use { target ->
source.copyTo(target)
private fun bootstrap(activity: Activity): Boolean {
val filesDir = activity.filesDir.absolutePath
val nativeLibsDir = activity.applicationInfo.nativeLibraryDir
activity.runOnUiThread { dialog = FramelessProgressDialog.show(activity, "") }
activity.assets.list(BUNDLE)?.let { bundle ->
for (asset in bundle) {
Logger.i("Asset found", asset)
val assetName = "$BUNDLE/$asset"
if (assetName != VERSION) {
when {
(assetName == MAP) -> {
activity.assets.open(assetName).bufferedReader().use { text ->
Logger.v("Reading symlink from $assetName")
text.forEachLine { line ->
try {
val array = line.split(" ").toTypedArray()
if (array.size == 2) {
val link = "$filesDir/${array[0]}"
val file = "$nativeLibsDir/${array[1]}"
if (File(file).exists()) {
FileUtils.symlink(link, file)
} else {
Logger.w("File $file does not exist, skipping")
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
(asset.endsWith("7z")) -> {
Logger.v("Uncompressing $assetName")
try {
val ok = (extract(activity.assets, assetName, filesDir) == 0)
if (!ok)
return false
} catch (e: Exception) {
Logger.e("Error extracting 7z file: %e")
return false
}
}
else -> {
Logger.v("Extracting $assetName")
try {
val file = File(filesDir, asset)
val inputStream = activity.assets.open(assetName)
val outputStream = FileOutputStream(file)
inputStream.use { source ->
outputStream.use { target ->
source.copyTo(target)
}
}
inputStream.close()
outputStream.flush()
outputStream.close()
} catch (e: IOException) {
Logger.w("Error copying $assetName:\n$e")
}
}
}
}
}
} catch (e: Exception) {
Logger.e(TAG, "Error copying file: $e")
}
activity.runOnUiThread { dialog?.dismiss() }
return true
}

/* dialog used while extracting assets from zip */
private var dialog: FramelessProgressDialog? = null
private class FramelessProgressDialog private constructor(context: Context):
Dialog(context, R.style.FramelessDialog) {
Expand Down Expand Up @@ -219,14 +159,5 @@ class Assets {
}
}

private fun showProgress(activity: Activity) {
activity.runOnUiThread {
dialog = FramelessProgressDialog.show(activity, "") }
}

private fun dismissProgress(activity: Activity) {
activity.runOnUiThread {
dialog?.dismiss()
}
}
private external fun extract(assetManager: AssetManager, payload: String, output: String): Int
}
Loading

0 comments on commit 0198bea

Please sign in to comment.