mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-16 04:52:39 +02:00
Fix QR code generation for large polycentric export bundles
- Add GZIP compression for large export data (>2000 chars) - Implement fallback QR generation with different error correction levels - Add automatic decompression support in import functionality - Improve error handling with fallback to text display - Add localized error messages for QR code failures - Add compression ratio logging for debugging This fixes the 'Data too big' error when generating QR codes for polycentric profile exports by automatically compressing large data and providing multiple fallback mechanisms.
This commit is contained in:
@@ -29,14 +29,19 @@ import com.futo.polycentric.core.StorageTypeCRDTSetItem
|
||||
import com.futo.polycentric.core.Store
|
||||
import com.futo.polycentric.core.toBase64Url
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.EncodeHintType
|
||||
import com.google.zxing.MultiFormatWriter
|
||||
import com.google.zxing.common.BitMatrix
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import userpackage.Protocol
|
||||
import userpackage.Protocol.ExportBundle
|
||||
import userpackage.Protocol.URLInfo
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import android.util.Base64
|
||||
|
||||
class PolycentricBackupActivity : AppCompatActivity() {
|
||||
private lateinit var _buttonShare: BigButton;
|
||||
@@ -74,6 +79,8 @@ class PolycentricBackupActivity : AppCompatActivity() {
|
||||
try {
|
||||
val pair = withContext(Dispatchers.IO) {
|
||||
val bundle = createExportBundle()
|
||||
Logger.i(TAG, "Export bundle created, length: ${bundle.length}")
|
||||
|
||||
val dimension = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 200f, resources.displayMetrics
|
||||
).toInt()
|
||||
@@ -89,10 +96,22 @@ class PolycentricBackupActivity : AppCompatActivity() {
|
||||
_buttonCopy.visibility = View.VISIBLE
|
||||
} catch (e: Exception) {
|
||||
Logger.e(TAG, getString(R.string.failed_to_generate_qr_code), e)
|
||||
|
||||
// Show the export bundle text even if QR code generation fails
|
||||
_exportBundle = withContext(Dispatchers.IO) { createExportBundle() }
|
||||
|
||||
// Provide more specific error message based on the exception
|
||||
val errorMessage = when {
|
||||
e.message?.contains("Data too big") == true -> getString(R.string.qr_code_too_large_use_text_below)
|
||||
else -> getString(R.string.failed_to_generate_qr_code)
|
||||
}
|
||||
_textQR.text = errorMessage
|
||||
_textQR.visibility = View.VISIBLE
|
||||
_buttonShare.visibility = View.VISIBLE
|
||||
_buttonCopy.visibility = View.VISIBLE
|
||||
|
||||
// Hide QR image since generation failed
|
||||
_imageQR.visibility = View.INVISIBLE
|
||||
_textQR.visibility = View.INVISIBLE
|
||||
_buttonShare.visibility = View.INVISIBLE
|
||||
_buttonCopy.visibility = View.INVISIBLE
|
||||
} finally {
|
||||
_loader.visibility = View.GONE
|
||||
}
|
||||
@@ -111,8 +130,33 @@ class PolycentricBackupActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun generateQRCode(content: String, width: Int, height: Int): Bitmap {
|
||||
val bitMatrix = MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height);
|
||||
return bitMatrixToBitmap(bitMatrix);
|
||||
// Try different error correction levels and settings to handle large data
|
||||
val errorCorrectionLevels = listOf(
|
||||
ErrorCorrectionLevel.L, // 7% recovery
|
||||
ErrorCorrectionLevel.M, // 15% recovery
|
||||
ErrorCorrectionLevel.Q, // 25% recovery
|
||||
ErrorCorrectionLevel.H // 30% recovery
|
||||
)
|
||||
|
||||
var lastException: Exception? = null
|
||||
|
||||
for (errorLevel in errorCorrectionLevels) {
|
||||
try {
|
||||
val hints = java.util.EnumMap<EncodeHintType, Any>(EncodeHintType::class.java)
|
||||
hints[EncodeHintType.ERROR_CORRECTION] = errorLevel
|
||||
hints[EncodeHintType.MARGIN] = 1
|
||||
|
||||
val bitMatrix = MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints)
|
||||
return bitMatrixToBitmap(bitMatrix)
|
||||
} catch (e: Exception) {
|
||||
lastException = e
|
||||
Logger.w(TAG, "Failed to generate QR code with error correction level $errorLevel: ${e.message}")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If all attempts fail, throw the last exception
|
||||
throw lastException ?: Exception("Failed to generate QR code")
|
||||
}
|
||||
|
||||
private fun bitMatrixToBitmap(matrix: BitMatrix): Bitmap {
|
||||
@@ -203,7 +247,31 @@ class PolycentricBackupActivity : AppCompatActivity() {
|
||||
.setBody(exportBundle.toByteString())
|
||||
.build();
|
||||
|
||||
return "polycentric://" + urlInfo.toByteArray().toBase64Url()
|
||||
val originalData = urlInfo.toByteArray()
|
||||
val originalUrl = "polycentric://" + originalData.toBase64Url()
|
||||
|
||||
// If the original URL is too long, try compression
|
||||
if (originalUrl.length > 2000) { // QR code practical limit
|
||||
try {
|
||||
val compressedData = compressData(originalData)
|
||||
val compressedUrl = "polycentric://" + compressedData.toBase64Url()
|
||||
val compressionRatio = (compressedUrl.length.toFloat() / originalUrl.length * 100).toInt()
|
||||
Logger.i(TAG, "Using compressed export bundle. Original size: ${originalUrl.length}, Compressed size: ${compressedUrl.length}, Compression ratio: ${compressionRatio}%")
|
||||
return compressedUrl
|
||||
} catch (e: Exception) {
|
||||
Logger.w(TAG, "Failed to compress export bundle, using original", e)
|
||||
}
|
||||
}
|
||||
|
||||
return originalUrl
|
||||
}
|
||||
|
||||
private fun compressData(data: ByteArray): ByteArray {
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
GZIPOutputStream(outputStream).use { gzip ->
|
||||
gzip.write(data)
|
||||
}
|
||||
return outputStream.toByteArray()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
+31
-1
@@ -30,6 +30,8 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import userpackage.Protocol
|
||||
import userpackage.Protocol.ExportBundle
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
class PolycentricImportProfileActivity : AppCompatActivity() {
|
||||
private lateinit var _buttonHelp: ImageButton;
|
||||
@@ -108,7 +110,20 @@ class PolycentricImportProfileActivity : AppCompatActivity() {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val data = url.substring("polycentric://".length).base64UrlToByteArray();
|
||||
val urlInfo = Protocol.URLInfo.parseFrom(data);
|
||||
|
||||
// Try to parse as regular data first, if it fails, try decompressing
|
||||
val urlInfo = try {
|
||||
Protocol.URLInfo.parseFrom(data)
|
||||
} catch (e: Exception) {
|
||||
// If parsing fails, try to decompress the data
|
||||
try {
|
||||
val decompressedData = decompressData(data)
|
||||
Protocol.URLInfo.parseFrom(decompressedData)
|
||||
} catch (decompressException: Exception) {
|
||||
throw Exception("Failed to parse URL data: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
if (urlInfo.urlType != 3L) {
|
||||
throw Exception("Expected urlInfo struct of type ExportBundle")
|
||||
}
|
||||
@@ -163,6 +178,21 @@ class PolycentricImportProfileActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun decompressData(data: ByteArray): ByteArray {
|
||||
val inputStream = ByteArrayInputStream(data)
|
||||
val outputStream = java.io.ByteArrayOutputStream()
|
||||
|
||||
GZIPInputStream(inputStream).use { gzip ->
|
||||
val buffer = ByteArray(8192) // 8KB buffer
|
||||
var bytesRead: Int
|
||||
while (gzip.read(buffer).also { bytesRead = it } != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead)
|
||||
}
|
||||
}
|
||||
|
||||
return outputStream.toByteArray()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PolycentricImportProfileActivity";
|
||||
}
|
||||
|
||||
@@ -404,6 +404,7 @@
|
||||
<string name="unknown_reconstruction_type">Unbekannter Rekonstruktionstyp</string>
|
||||
<string name="failed_to_parse_newpipe_subscriptions">Fehler beim Parsen von NewPipe-Abonnements</string>
|
||||
<string name="failed_to_generate_qr_code">Fehler beim Generieren des QR-Codes</string>
|
||||
<string name="qr_code_too_large_use_text_below">QR-Code zu groß. Verwenden Sie den Text unten, um Ihr Profil zu teilen.</string>
|
||||
<string name="share_text">Text teilen</string>
|
||||
<string name="copied_text">Text kopiert</string>
|
||||
<string name="must_be_at_least_3_characters_long">Muss mindestens 3 Zeichen lang sein.</string>
|
||||
|
||||
@@ -381,6 +381,7 @@
|
||||
<string name="unknown_reconstruction_type">Tipo de reconstrucción desconocido</string>
|
||||
<string name="failed_to_parse_newpipe_subscriptions">Error al analizar las suscripciones de NewPipe</string>
|
||||
<string name="failed_to_generate_qr_code">Error al generar el código QR</string>
|
||||
<string name="qr_code_too_large_use_text_below">Código QR demasiado grande. Use el texto de abajo para compartir su perfil.</string>
|
||||
<string name="share_text">Compartir texto</string>
|
||||
<string name="copied_text">Texto copiado</string>
|
||||
<string name="must_be_at_least_3_characters_long">Debe tener al menos 3 caracteres de longitud.</string>
|
||||
|
||||
@@ -420,6 +420,7 @@
|
||||
<string name="unknown_reconstruction_type">Type de reconstruction inconnu</string>
|
||||
<string name="failed_to_parse_newpipe_subscriptions">Échec de l\'analyse des abonnements NewPipe</string>
|
||||
<string name="failed_to_generate_qr_code">Échec de la génération du code QR</string>
|
||||
<string name="qr_code_too_large_use_text_below">Code QR trop volumineux. Utilisez le texte ci-dessous pour partager votre profil.</string>
|
||||
<string name="share_text">Partager le texte</string>
|
||||
<string name="copied_text">Texte copié</string>
|
||||
<string name="must_be_at_least_3_characters_long">Doit comporter au moins 3 caractères.</string>
|
||||
|
||||
@@ -642,6 +642,7 @@
|
||||
<string name="failed_to_parse_text_file">Failed to parse text file</string>
|
||||
<string name="failed_to_parse_newpipe_subscriptions">Failed to parse NewPipe Subscriptions</string>
|
||||
<string name="failed_to_generate_qr_code">Failed to generate QR code</string>
|
||||
<string name="qr_code_too_large_use_text_below">QR code too large. Use the text below to share your profile.</string>
|
||||
<string name="share_text">Share Text</string>
|
||||
<string name="copied_text">Copied Text</string>
|
||||
<string name="must_be_at_least_3_characters_long">Must be at least 3 characters long.</string>
|
||||
|
||||
Reference in New Issue
Block a user