[+] Upload

This commit is contained in:
2025-10-26 12:31:16 +08:00
parent 5fbacee18f
commit 6f2037a9dc
4 changed files with 86 additions and 9 deletions
+2
View File
@@ -3,8 +3,10 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:usesCleartextTraffic="true"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
@@ -38,11 +38,13 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import aza.instant.network.BackendClient
import aza.instant.ui.theme.ProjectInstantTheme
import kotlinx.coroutines.launch
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import java.util.UUID
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -65,6 +67,7 @@ fun FileListerScreen(modifier: Modifier = Modifier) {
var picture by remember { mutableStateOf<Picture?>(null) }
val scrollState = rememberScrollState()
val coroutineScope = rememberCoroutineScope()
val backendClient = remember { BackendClient() }
Column(
modifier = modifier.fillMaxSize(),
@@ -121,16 +124,25 @@ fun FileListerScreen(modifier: Modifier = Modifier) {
picture = renderSvgAndroid(context, svg)
// Save picture to file
val file = File(latestImage.parent, "${latestImage.nameWithoutExtension}.framed.${System.currentTimeMillis()}.jpg")
file.createNewFile()
file.outputStream().use { outputStream ->
val renderedImageFile = File(latestImage.parent, "${latestImage.nameWithoutExtension}.framed.${System.currentTimeMillis()}.jpg")
renderedImageFile.createNewFile()
renderedImageFile.outputStream().use { outputStream ->
Bitmap.createBitmap(picture!!).compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
}
feedbackText = "Rendered ${latestImage.name}"
feedbackText = "Rendered ${latestImage.name}, now uploading..."
val response = backendClient.uploadImage(
id = UUID.randomUUID().toString(),
ownerKey = "1234",
originalPhoto = latestImage,
editedPhoto = renderedImageFile
)
feedbackText = "Upload response: ${response.status}"
} catch (e: Exception) {
feedbackText = "Error rendering: ${e.message}"
Log.e("MainActivity", "Error rendering", e)
feedbackText = "Error during operation: ${e.message}"
Log.e("MainActivity", "Error during render or upload", e)
}
} else {
feedbackText = "No images found in CA_IMAGES"
@@ -138,7 +150,7 @@ fun FileListerScreen(modifier: Modifier = Modifier) {
}
},
) {
Text(text = "Render Latest Image")
Text(text = "Render and Upload")
}
}
@@ -0,0 +1,60 @@
package aza.instant.network
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.HttpTimeoutConfig
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.timeout
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
import java.io.File
class BackendClient {
private val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}
companion object {
private const val API_KEY = "meowmeow"
private const val SERVER_URL = "http://172.16.0.212:3000" // For Android emulator
}
suspend fun uploadImage(
id: String,
ownerKey: String,
originalPhoto: File,
editedPhoto: File
): HttpResponse {
val origB = originalPhoto.readBytes()
val editB = editedPhoto.readBytes()
println(origB.size)
println(editB.size)
return client.post("$SERVER_URL/upload") {
headers {
append("x-instant-key", API_KEY)
}
setBody(
MultiPartFormDataContent(
formData {
append("id", id)
append("owner_key", ownerKey)
append("\"photo\"", originalPhoto.name, ContentType.Image.JPEG, origB.size.toLong()) { write(origB) }
append("\"edited_photo\"", editedPhoto.name, ContentType.Image.JPEG, editB.size.toLong()) { write(editB) }
}
)
)
timeout { HttpTimeoutConfig(1 * 60 * 1000L) }
}
}
}
+5 -2
View File
@@ -72,7 +72,7 @@ async function getExifData(file: File): Promise<any> {
const INSTANT_KEY = process.env.INSTANT_KEY!
if (!await exists(FOLDER_PHOTOS)) await mkdir(FOLDER_PHOTOS, { recursive: true })
console.log("Server starting with valid 'secrets.json'.")
console.log("Server started.")
function checkHeaderKey(headers: Record<string, string | undefined>, expectList: string[] = [INSTANT_KEY]) {
const key = headers["x-instant-key"]
@@ -80,13 +80,16 @@ function checkHeaderKey(headers: Record<string, string | undefined>, expectList:
}
export const app = new Elysia()
.get("/", () => "Running!")
// Error handling: Return status code and message
.onError(({ error, status }) => {
if (error instanceof HttpError) {
if (error.content instanceof String) return status(error.status, { msg: error.message })
else return status(error.status, error.content)
}
else return status(500, { error: "Internal Server Error" })
// else return status(500, { error: "Internal Server Error" })
else throw error
})
.post("/upload", async ({ body, headers, status }) => {