Commit aa86b235 authored by Jonas L.'s avatar Jonas L.

Migrate to Kotlin

parent dd54ef82
......@@ -13,9 +13,9 @@ android {
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas".toString())
}
}
}
......
package de.determapp.android.content
import android.util.JsonReader
import de.determapp.android.content.projectdata.*
import java.io.IOException
import java.util.*
object ContentJsonParser {
const val CONTENT_JSON_PATH = "./data.json"
fun parseProject(reader: JsonReader): Project {
reader.use {
var title: String? = null
var startQuestionId: String? = null
var result: Map<String, Result>? = null
var image: Map<String, Image>? = null
var question: Map<String, Question>? = null
var imprint: String? = null
var description: String? = null
var hash: String? = null
var projectId: String? = null
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
"title" -> title = reader.nextString()
"startQuestionId" -> startQuestionId = reader.nextString()
"result" -> result = parseResults(reader)
"image" -> image = parseImages(reader)
"question" -> question = parseQuestions(reader)
"imprint" -> imprint = reader.nextString()
"description" -> description = reader.nextString()
"hash" -> hash = reader.nextString()
"projectId" -> projectId = reader.nextString()
else -> reader.skipValue()
}
}
reader.endObject()
return Project(
title = title!!,
startQuestionId = startQuestionId!!,
result = result!!,
image = image!!,
question = question!!,
imprint = imprint!!,
description = description!!,
hash = hash!!,
projectId = projectId!!
)
}
}
private fun parseImage(reader: JsonReader): Image {
var previewDataUrl: String? = null
var resolution: List<ImageResolution>? = null
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
"previewDataUrl" -> previewDataUrl = reader.nextString()
"resolution" -> resolution = parseImageResolutions(reader)
else -> reader.skipValue()
}
}
reader.endObject()
return Image(
previewDataUrl = previewDataUrl!!,
resolution = resolution!!
)
}
private fun parseImages(reader: JsonReader): Map<String, Image> {
val result = HashMap<String, Image>()
reader.beginObject()
while (reader.hasNext()) {
val id = reader.nextName()
val image = parseImage(reader)
result[id] = image
}
reader.endObject()
return Collections.unmodifiableMap(result)
}
private fun parseImageResolution(reader: JsonReader): ImageResolution {
var width: Int? = null
var height: Int? = null
var filename: String? = null
var sha512: String? = null
var fileSize: Int? = null
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
"width" -> width = reader.nextInt()
"height" -> height = reader.nextInt()
"filename" -> filename = reader.nextString()
"sha512" -> sha512 = reader.nextString()
"fileSize" -> fileSize = reader.nextInt()
else -> reader.skipValue()
}
}
reader.endObject()
return ImageResolution(
width = width!!,
height = height!!,
filename = filename!!,
sha512 = sha512!!,
fileSize = fileSize!!.toLong()
)
}
@Throws(IOException::class)
private fun parseImageResolutions(reader: JsonReader): List<ImageResolution> {
val result = ArrayList<ImageResolution>()
reader.beginArray()
while (reader.hasNext()) {
result.add(parseImageResolution(reader))
}
reader.endArray()
return Collections.unmodifiableList(result)
}
private fun parseResult(reader: JsonReader): Result {
var title: String? = null
var subtitle: String? = null
var info: List<ResultInfo>? = null
var image: List<ResultImage>? = null
var url: String? = null
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
"title" -> title = reader.nextString()
"subtitle" -> subtitle = reader.nextString()
"info" -> info = parseResultInfos(reader)
"image" -> image = parseResultImages(reader)
"url" -> url = reader.nextString()
else -> reader.skipValue()
}
}
reader.endObject()
return Result(
title = title!!,
subtitle = subtitle!!,
info = info!!,
image = image!!,
url = url!!
)
}
private fun parseResults(reader: JsonReader): Map<String, Result> {
val result = HashMap<String, Result>()
reader.beginObject()
while (reader.hasNext()) {
val id = reader.nextName()
val resultItem = parseResult(reader)
result[id] = resultItem
}
reader.endObject()
return Collections.unmodifiableMap(result)
}
private fun parseResultInfo(reader: JsonReader): ResultInfo {
var title: String? = null
var content: String? = null
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
"title" -> title = reader.nextString()
"content" -> content = reader.nextString()
else -> reader.skipValue()
}
}
reader.endObject()
return ResultInfo(
title = title!!,
content = content!!
)
}
private fun parseResultInfos(reader: JsonReader): List<ResultInfo> {
val result = ArrayList<ResultInfo>()
reader.beginArray()
while (reader.hasNext()) {
result.add(parseResultInfo(reader))
}
reader.endArray()
return Collections.unmodifiableList(result)
}
private fun parseResultImage(reader: JsonReader): ResultImage {
var image: String? = null
var text: String? = null
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
"image" -> image = reader.nextString()
"text" -> text = reader.nextString()
else -> reader.skipValue()
}
}
reader.endObject()
return ResultImage(
image = image!!,
text = text!!
)
}
private fun parseResultImages(reader: JsonReader): List<ResultImage> {
val result = ArrayList<ResultImage>()
reader.beginArray()
while (reader.hasNext()) {
result.add(parseResultImage(reader))
}
reader.endArray()
return Collections.unmodifiableList(result)
}
private fun parseQuestion(reader: JsonReader): Question {
var text: String? = null
var note: String? = null
var answers: List<QuestionAnswer>? = null
var url: String? = null
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
"text" -> text = reader.nextString()
"note" -> note = reader.nextString()
"answer" -> answers = parseQuestionAnswers(reader)
"url" -> url = reader.nextString()
else -> reader.skipValue()
}
}
reader.endObject()
return Question(
text = text!!,
note = note!!,
answer = answers!!,
url = url!!
)
}
private fun parseQuestions(reader: JsonReader): Map<String, Question> {
val result = HashMap<String, Question>()
reader.beginObject()
while (reader.hasNext()) {
val id = reader.nextName()
val question = parseQuestion(reader)
result[id] = question
}
reader.endObject()
return Collections.unmodifiableMap(result)
}
private fun parseQuestionAnswer(reader: JsonReader): QuestionAnswer {
var text: String? = null
var image: String? = null
var question: String? = null
var result: String? = null
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
"text" -> text = reader.nextString()
"image" -> image = reader.nextString()
"question" -> question = reader.nextString()
"result" -> result = reader.nextString()
else -> reader.skipValue()
}
}
reader.endObject()
return QuestionAnswer(
text = text!!,
image = image,
question = question,
result = result
)
}
private fun parseQuestionAnswers(reader: JsonReader): List<QuestionAnswer> {
val result = ArrayList<QuestionAnswer>()
reader.beginArray()
while (reader.hasNext()) {
result.add(parseQuestionAnswer(reader))
}
reader.endArray()
return Collections.unmodifiableList(result)
}
}
package de.determapp.android.content;
import android.content.Context;
import androidx.core.content.ContextCompat;
import android.util.Log;
import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import de.determapp.android.BuildConfig;
import de.determapp.android.content.database.AppDatabase;
import de.determapp.android.content.database.AppDatabaseInstance;
import de.determapp.android.content.database.DownloadedLocalNetworkProject;
import de.determapp.android.content.database.PackageSourceProject;
import de.determapp.android.ui.viewer.ProjectSpec;
import de.determapp.android.util.Async;
public class ContentStorage {
private static final String LOG_TAG = "DataStorage";
private static ContentStorage instance;
public static synchronized ContentStorage with(Context context) {
if (instance == null) {
instance = new ContentStorage(context.getApplicationContext());
}
return instance;
}
private final AppDatabase database;
public final File contentFilesDirectory;
public final File imageFilesBaseDirectory;
// new items should be added before they are written
// items should only be removed when they are deleted manually and not referenced anywhere
public final Set<String> contentFilesToKeep = Collections.synchronizedSet(new HashSet<>());
private ContentStorage(Context context) {
contentFilesDirectory = new File(ContextCompat.getDataDir(context), "determapp_content");
imageFilesBaseDirectory = new File(context.getExternalFilesDir(null), "determapp_image");
database = AppDatabaseInstance.get(context);
contentFilesDirectory.mkdirs();
imageFilesBaseDirectory.mkdirs();
Async.INSTANCE.getDisk().submit((Runnable) this::cleanUpContentFilesStorage);
}
public File getImageDirectory(ProjectSpec project) {
return new File(
imageFilesBaseDirectory,
project.key()
);
}
public static String generateId() {
return UUID.randomUUID().toString();
}
private void cleanUpContentFilesStorage() {
for(DownloadedLocalNetworkProject item: database.localNetworkProjectDao().getProjects()) {
contentFilesToKeep.add(item.localFilename);
}
for(PackageSourceProject item: database.packageSourceProjectDao().getAllSync()) {
if (item.localFilename != null) {
contentFilesToKeep.add(item.localFilename);
}
}
cleanUpContentFilesStorage(contentFilesDirectory, contentFilesToKeep);
}
private void cleanUpContentFilesStorage(File directory, Set<String> filesToKeep) {
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "cleanUp(" + directory + ")");
}
for (File file: directory.listFiles()) {
if (!filesToKeep.contains(file.getName())) {
if (!file.delete()) {
if (BuildConfig.DEBUG) {
Log.w(LOG_TAG, "Couldn't delete old file: " + file);
}
}
}
}
}
}
package de.determapp.android.content
import android.content.Context
import android.util.Log
import androidx.core.content.ContextCompat
import de.determapp.android.BuildConfig
import de.determapp.android.content.database.AppDatabaseInstance
import de.determapp.android.ui.viewer.ProjectSpec
import de.determapp.android.util.Async
import java.io.File
import java.util.*
class ContentStorage private constructor(context: Context) {
private val database = AppDatabaseInstance.with(context)
val contentFilesDirectory = File(ContextCompat.getDataDir(context), "determapp_content")
val imageFilesBaseDirectory = File(context.getExternalFilesDir(null), "determapp_image")
// new items should be added before they are written
// items should only be removed when they are deleted manually and not referenced anywhere
val contentFilesToKeep: MutableSet<String> = Collections.synchronizedSet(HashSet())
init {
contentFilesDirectory.mkdirs()
imageFilesBaseDirectory.mkdirs()
Async.disk.submit { this.cleanUpContentFilesStorage() }
}
fun getImageDirectory(project: ProjectSpec): File {
return File(
imageFilesBaseDirectory,
project.key()
)
}
private fun cleanUpContentFilesStorage() {
for (item in database.localNetworkProjectDao().getProject()) {
contentFilesToKeep.add(item.localFilename)
}
for (item in database.packageSourceProjectDao().getAllSync()) {
if (item.localFilename != null) {
contentFilesToKeep.add(item.localFilename)
}
}
cleanUpContentFilesStorage(contentFilesDirectory, contentFilesToKeep)
}
private fun cleanUpContentFilesStorage(directory: File, filesToKeep: Set<String>) {
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "cleanUp($directory)")
}
for (file in directory.listFiles()) {
if (!filesToKeep.contains(file.name)) {
if (!file.delete()) {
if (BuildConfig.DEBUG) {
Log.w(LOG_TAG, "Couldn't delete old file: $file")
}
}
}
}
}
companion object {
private val LOG_TAG = "DataStorage"
private var instance: ContentStorage? = null
@Synchronized
fun with(context: Context): ContentStorage {
if (instance == null) {
instance = ContentStorage(context.applicationContext)
}
return instance!!
}
fun generateId(): String {
return UUID.randomUUID().toString()
}
}
}
package de.determapp.android.content.cleanup
import androidx.lifecycle.MutableLiveData
import android.content.Context
import androidx.lifecycle.MutableLiveData
import de.determapp.android.content.ContentStorage
import de.determapp.android.content.ProjectLocks
import de.determapp.android.content.database.AppDatabaseInstance
......@@ -16,7 +16,7 @@ object DeleteDownloadedProject {
val projectsScheduledForDeletion = MutableLiveData<Set<ProjectSpec>>()
fun deleteDownloadedProjectSync(spec: ProjectSpec, context: Context) {
val db = AppDatabaseInstance.get(context)
val db = AppDatabaseInstance.with(context)
val storage = ContentStorage.with(context)
runOnUiThread(Runnable {
......
package de.determapp.android.content.database;
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(entities = {DownloadedLocalNetworkProject.class, PackageSourceProject.class, PackageSource.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract DownloadedLocalNetworkProjectDao localNetworkProjectDao();
public abstract PackageSourceDao packageSourceDao();
public abstract PackageSourceProjectDao packageSourceProjectDao();
}
package de.determapp.android.content.database
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(
entities = [
DownloadedLocalNetworkProject::class,
PackageSourceProject::class,
PackageSource::class
],
version = 1
)
abstract class AppDatabase : RoomDatabase() {
abstract fun localNetworkProjectDao(): DownloadedLocalNetworkProjectDao
abstract fun packageSourceDao(): PackageSourceDao
abstract fun packageSourceProjectDao(): PackageSourceProjectDao
}
package de.determapp.android.content.database;
import androidx.room.Room;
import android.content.Context;
public abstract class AppDatabaseInstance {
private AppDatabaseInstance() {
// do not construct this
}
private static AppDatabase instance;
public static synchronized AppDatabase get(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(
context.getApplicationContext(),
AppDatabase.class,
"db"
).build();
}
return instance;
}
}
package de.determapp.android.content.database
import android.content.Context
import androidx.room.Room
object AppDatabaseInstance {
private var instance: AppDatabase? = null
@Synchronized
fun with(context: Context): AppDatabase {
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"db"
).build()
}
return instance!!
}
}
package de.determapp.android.content.database;
package de.determapp.android.content.database
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "downloaded_local_network_project")
public class DownloadedLocalNetworkProject {
data class DownloadedLocalNetworkProject(
@PrimaryKey
@ColumnInfo(name = "project_id")
@NonNull
public String projectId;
@NonNull
public String hash;
val projectId: String,
val hash: String,
// when installing an update, the new version
// is saved under a new filename and this
// field is updated afterwards
@ColumnInfo(name = "local_filename")
@NonNull
public String localFilename;
val localFilename: String,
@ColumnInfo(name = "file_size")
@NonNull
public long fileSize;
@NonNull
public String title;
@NonNull
public String description;
}
val fileSize: Long = 0,
val title: String,
val description: String
)