diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f28a23ce3..1c8a1598c 100755 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -4,6 +4,10 @@ -dontnote ** -dontobfuscate +-keepattributes Signature +-keepattributes Exceptions +-keepattributes *Annotation* + -keep class javax.** { *; } -keep class jdkx.** { *; } @@ -35,7 +39,6 @@ } # EventBus --keepattributes *Annotation* -keepclassmembers class ** { @org.greenrobot.eventbus.Subscribe ; } @@ -91,19 +94,29 @@ # Retrofit 2 -dontwarn retrofit2.** -keep class retrofit2.** { *; } --keepattributes Signature --keepattributes Exceptions -keepclasseswithmembers class * { @retrofit2.http.* ; } # OkHttp3 --keepattributes Signature --keepattributes *Annotation* -keep class okhttp3.** { *; } -keep interface okhttp3.** { *; } -dontwarn okhttp3.** # Stat uploader --keep class com.itsaky.androidide.stats.** { *; } \ No newline at end of file +-keep class com.itsaky.androidide.stats.** { *; } + +# Gson +-keep class * extends com.google.gson.TypeAdapter +-keep class * implements com.google.gson.TypeAdapterFactory +-keep class * implements com.google.gson.JsonSerializer +-keep class * implements com.google.gson.JsonDeserializer + +-keepclassmembers,allowobfuscation class * { + @com.google.gson.annotations.SerializedName ; +} + +## Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher. +-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken +-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken \ No newline at end of file diff --git a/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt index c55369b0d..37eee9b9e 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt @@ -163,15 +163,13 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { try { if (viewModel.openedFilesCache != null) { - viewModel.openedFilesCache?.apply { - this.allFiles.forEach { openFile(File(it.filePath), it.selection) } - openFile(File(selectedFile)) + viewModel.openedFilesCache?.let { cache -> + onReadOpenedFilesCache(cache) } } else { - viewModel.readOpenedFiles { - it ?: return@readOpenedFiles - it.allFiles.forEach { file -> openFile(File(file.filePath), file.selection) } - openFile(File(it.selectedFile)) + viewModel.readOpenedFiles { cache -> + cache ?: return@readOpenedFiles + onReadOpenedFilesCache(cache) } } viewModel.openedFilesCache = null @@ -180,6 +178,13 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } } + private fun onReadOpenedFilesCache(cache: OpenedFilesCache) { + cache.allFiles.forEach { file -> + openFile(File(file.filePath), file.selection) + } + openFile(File(cache.selectedFile)) + } + override fun onPrepareOptionsMenu(menu: Menu): Boolean { ensureToolbarMenu(menu) return true diff --git a/app/src/main/java/com/itsaky/androidide/models/OpenedFile.kt b/app/src/main/java/com/itsaky/androidide/models/OpenedFile.kt index 0853914f9..da069a64a 100644 --- a/app/src/main/java/com/itsaky/androidide/models/OpenedFile.kt +++ b/app/src/main/java/com/itsaky/androidide/models/OpenedFile.kt @@ -17,7 +17,10 @@ package com.itsaky.androidide.models +import com.google.gson.Gson import com.google.gson.annotations.SerializedName +import com.google.gson.stream.JsonReader +import com.itsaky.androidide.utils.ILogger /** * A file that is opened in editor. @@ -25,6 +28,37 @@ import com.google.gson.annotations.SerializedName * @author Akash Yadav */ data class OpenedFile( - @SerializedName("file") val filePath: String, - @SerializedName("selection") var selection: Range -) + @SerializedName(KEY_FILE) val filePath: String, + @SerializedName(KEY_SELECTION) var selection: Range +) { + + companion object { + + private const val KEY_FILE = "file" + private const val KEY_SELECTION = "selection" + private val log = ILogger.newInstance("OpenedFile") + + fun readFrom(reader: JsonReader): OpenedFile? { + return try { +// reader.beginObject() +// var path = "" +// var selection = Range.NONE +// while(reader.hasNext()) { +// val name = reader.nextName() +// if (name == KEY_FILE) { +// path = reader.nextString() +// } else if (name == KEY_SELECTION) { +// selection = Gson().fromJson(reader, Range::class.java) +// } +// } +// reader.endObject() +// +// OpenedFile(path, selection) + Gson().fromJson(reader, OpenedFile::class.java) + } catch (err: Exception) { + log.error("Failed to read opened file", err) + null + } + } + } +} diff --git a/app/src/main/java/com/itsaky/androidide/models/OpenedFilesCache.kt b/app/src/main/java/com/itsaky/androidide/models/OpenedFilesCache.kt index 448cb70f1..4ac4a588f 100644 --- a/app/src/main/java/com/itsaky/androidide/models/OpenedFilesCache.kt +++ b/app/src/main/java/com/itsaky/androidide/models/OpenedFilesCache.kt @@ -17,7 +17,56 @@ package com.itsaky.androidide.models +import com.google.gson.Gson +import com.google.gson.annotations.SerializedName +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import com.itsaky.androidide.utils.ILogger +import java.io.Reader + /** * @author Akash Yadav */ -data class OpenedFilesCache(val selectedFile: String, val allFiles: List) \ No newline at end of file +data class OpenedFilesCache(@SerializedName(KEY_SELECTED_FILE) val selectedFile: String, + @SerializedName(KEY_ALL_FILES) val allFiles: List) { + + companion object { + + private const val KEY_SELECTED_FILE = "selectedFile" + private const val KEY_ALL_FILES = "allFiles" + private val log = ILogger.newInstance("OpenedFilesCache") + + @JvmStatic + fun parse(contentReader: Reader): OpenedFilesCache? { + return try { + JsonReader(contentReader).use { reader -> + if (!reader.hasNext()) { + return@use null + } + + reader.beginObject() + var selectedFile = "" + var allFiles = emptyList() + while (reader.hasNext()) { + val name = reader.nextName() + + if (name == KEY_SELECTED_FILE) { + selectedFile = reader.nextString() + } else if (name == KEY_ALL_FILES) { + allFiles = Gson().fromJson(reader, object : TypeToken>() {}) + } + } + reader.endObject() + + return@use OpenedFilesCache(selectedFile, allFiles) + } + } catch (err: Exception) { + log.error("Failed to parse opened files cache", err) + null + } + } + + private fun readOpenedFiles(reader: JsonReader, allFiles: MutableList) { + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt b/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt index 5f13bf72f..4e4218eac 100644 --- a/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt +++ b/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt @@ -25,6 +25,7 @@ import androidx.lifecycle.ViewModel import com.blankj.utilcode.util.FileUtils import com.google.gson.Gson import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken import com.itsaky.androidide.models.OpenedFilesCache import com.itsaky.androidide.projects.ProjectManager import com.itsaky.androidide.tasks.executeAsync @@ -199,11 +200,13 @@ class EditorViewModel : ViewModel() { fun readOpenedFiles(result: (OpenedFilesCache?) -> Unit) { executeAsync({ - val file = getOpenedFilesCache() + val file = getOpenedFilesCache(false) if (file.length() == 0L) { return@executeAsync null } - return@executeAsync Gson().fromJson(file.readText(), OpenedFilesCache::class.java) + return@executeAsync file.reader().buffered().use { reader -> + OpenedFilesCache.parse(reader) + } }) { result(it) } @@ -224,17 +227,17 @@ class EditorViewModel : ViewModel() { } } - private fun getOpenedFilesCache(): File { + private fun getOpenedFilesCache(forWrite: Boolean = false): File { var file = Environment.getProjectCacheDir(ProjectManager.projectPath) file = File(file, "editor/openedFiles.json") - if (file.exists()) { + if (file.exists() && forWrite) { FileUtils.rename(file, "${file.name}.bak") } if (file.parentFile?.exists() == false) { file.parentFile?.mkdirs() } - + file.createNewFile() return file diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index e0f6284ba..2c465e8ad 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -24,6 +24,7 @@ plugins { dependencies { implementation(libs.androidx.annotation) implementation(libs.google.guava) + implementation(libs.google.gson) implementation(projects.logger) testImplementation(libs.tests.google.truth) diff --git a/shared/src/main/java/com/itsaky/androidide/models/Locations.kt b/shared/src/main/java/com/itsaky/androidide/models/Locations.kt index ee588f4a3..648832671 100644 --- a/shared/src/main/java/com/itsaky/androidide/models/Locations.kt +++ b/shared/src/main/java/com/itsaky/androidide/models/Locations.kt @@ -17,12 +17,16 @@ package com.itsaky.androidide.models +import com.google.gson.annotations.SerializedName import java.nio.file.Path data class Location(var file: Path, var range: Range) -data class Position @JvmOverloads constructor(var line: Int, var column: Int, var index: Int = -1) : - Comparable { +data class Position @JvmOverloads constructor( + @SerializedName("line") var line: Int, + @SerializedName("column") var column: Int, + @SerializedName("index") var index: Int = -1 +) : Comparable { fun requireIndex(): Int { if (index == -1) { @@ -43,7 +47,9 @@ data class Position @JvmOverloads constructor(var line: Int, var column: Int, va } companion object { - @JvmField val NONE = Position(-1, -1) + + @JvmField + val NONE = Position(-1, -1) } override fun compareTo(other: Position): Int { @@ -85,21 +91,26 @@ data class Position @JvmOverloads constructor(var line: Int, var column: Int, va open class Range @JvmOverloads -constructor(var start: Position = Position(0, 0), var end: Position = Position(0, 0)) : - Comparable { - - constructor(src: Range) : this(Position(src.start.line, src.start.column), Position(src.end.line, src.end.column)) +constructor( + @SerializedName("start") var start: Position = Position(0, 0), + @SerializedName("end") var end: Position = Position(0, 0) +) : Comparable { + + constructor(src: Range) : this(Position(src.start.line, src.start.column), + Position(src.end.line, src.end.column)) companion object { - @JvmField val NONE = Range(Position.NONE, Position.NONE) + + @JvmField + val NONE = Range(Position.NONE, Position.NONE) @JvmStatic - fun pointRange(line: Int, column: Int) : Range { + fun pointRange(line: Int, column: Int): Range { return pointRange(Position(line, column)) } @JvmStatic - fun pointRange(position: Position) : Range { + fun pointRange(position: Position): Range { return Range(position, position) } }