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:
austin
2025-10-28 18:26:36 -05:00
parent 7d19c2357c
commit 62a2f42d68
14 changed files with 117 additions and 7 deletions
@@ -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 {
@@ -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";
}
+1
View File
@@ -388,6 +388,7 @@
<string name="unhandled_exception_in_vs">استثناء غير معالج في VS</string>
<string name="send_exception_to_developers">إرسال الاستثناء للمطورين…</string>
<string name="your_license_key_has_been_set_an_app_restart_might_be_required">تم تعيين مفتاح الترخيص الخاص بك!\nقد يكون هناك حاجة لإعادة تشغيل التطبيق.</string>
<string name="qr_code_too_large_use_text_below">رمز الاستجابة السريعة كبير جدًا. استخدم النص أدناه لمشاركة ملفك الشخصي.</string>
<string name="invalid_license_format">تنسيق الترخيص غير صالح</string>
<string name="unknown_content_format">تنسيق المحتوى غير معروف</string>
<string name="unknown_file_format">تنسيق الملف غير معروف</string>
+1
View File
@@ -395,6 +395,7 @@
<string name="unhandled_exception_in_vs">Unbehandelte Ausnahme in VS</string>
<string name="send_exception_to_developers">Ausnahme an Entwickler senden…</string>
<string name="your_license_key_has_been_set_an_app_restart_might_be_required">Ihr Lizenzschlüssel wurde festgelegt!\nEin Neustart der App könnte erforderlich sein.</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="invalid_license_format">Ungültiges Lizenzformat</string>
<string name="unknown_content_format">Unbekanntes Inhaltsformat</string>
<string name="unknown_file_format">Unbekanntes Dateiformat</string>
+1
View File
@@ -372,6 +372,7 @@
<string name="unhandled_exception_in_vs">Excepción no manejada en VS</string>
<string name="send_exception_to_developers">Enviar excepción a los desarrolladores...</string>
<string name="your_license_key_has_been_set_an_app_restart_might_be_required">¡Se ha configurado tu clave de licencia!\nPuede ser necesario reiniciar la aplicación.</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="invalid_license_format">Formato de licencia no válido</string>
<string name="unknown_content_format">Formato de contenido desconocido</string>
<string name="unknown_file_format">Formato de archivo desconocido</string>
+1
View File
@@ -411,6 +411,7 @@
<string name="unhandled_exception_in_vs">Exception non gérée dans VS</string>
<string name="send_exception_to_developers">Envoyer l\'exception aux développeurs…</string>
<string name="your_license_key_has_been_set_an_app_restart_might_be_required">Votre clé de licence a été définie !\nUn redémarrage de l\'application peut être nécessaire.</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="invalid_license_format">Format de licence invalide</string>
<string name="unknown_content_format">Format de contenu inconnu</string>
<string name="unknown_file_format">Format de fichier inconnu</string>
+1
View File
@@ -617,6 +617,7 @@
<string name="unhandled_exception_in_vs">Eccezione non gestita in VS</string>
<string name="send_exception_to_developers">Invio eccezione agli sviluppatori…</string>
<string name="your_license_key_has_been_set_an_app_restart_might_be_required">La tua chiave di licenza è stata impostata!\nIl riavvio dell\'app potrebbe essere richiesto.</string>
<string name="qr_code_too_large_use_text_below">Codice QR troppo grande. Usa il testo qui sotto per condividere il tuo profilo.</string>
<string name="invalid_license_format">Formato licenza non valido</string>
<string name="unknown_content_format">Formato contenuto sconosciuto</string>
<string name="unknown_file_format">Formato file sconosciuto</string>
+1
View File
@@ -374,6 +374,7 @@
<string name="unhandled_exception_in_vs">VSで未処理の例外</string>
<string name="send_exception_to_developers">開発者に例外を送信…</string>
<string name="your_license_key_has_been_set_an_app_restart_might_be_required">ライセンスキーが設定されました!\nアプリを再起動する可能性があります。</string>
<string name="qr_code_too_large_use_text_below">QRコードが大きすぎます。下のテキストを使用してプロフィールを共有してください。</string>
<string name="invalid_license_format">無効なライセンス形式</string>
<string name="unknown_content_format">不明なコンテンツ形式</string>
<string name="unknown_file_format">不明なファイル形式</string>
+1
View File
@@ -410,6 +410,7 @@
<string name="unhandled_exception_in_vs">VS에서 처리되지 않은 예외</string>
<string name="send_exception_to_developers">개발자에게 예외를 보냅니다…</string>
<string name="your_license_key_has_been_set_an_app_restart_might_be_required">라이선스 키가 설정되었습니다!\n앱을 다시 시작해야 할 수 있습니다.</string>
<string name="qr_code_too_large_use_text_below">QR 코드가 너무 큽니다. 아래 텍스트를 사용하여 프로필을 공유하세요.</string>
<string name="invalid_license_format">잘못된 라이선스 형식</string>
<string name="unknown_content_format">알 수 없는 콘텐츠 형식</string>
<string name="unknown_file_format">알 수 없는 파일 형식</string>
+1
View File
@@ -407,6 +407,7 @@
<string name="unhandled_exception_in_vs">Exceção não tratada no VS</string>
<string name="send_exception_to_developers">Enviar exceção aos desenvolvedores…</string>
<string name="your_license_key_has_been_set_an_app_restart_might_be_required">Sua chave de licença foi definida!\nUma reinicialização do aplicativo pode ser necessária.</string>
<string name="qr_code_too_large_use_text_below">Código QR muito grande. Use o texto abaixo para compartilhar seu perfil.</string>
<string name="invalid_license_format">Formato de licença inválido</string>
<string name="unknown_content_format">Formato de conteúdo desconhecido</string>
<string name="unknown_file_format">Formato de arquivo desconhecido</string>
+1
View File
@@ -407,6 +407,7 @@
<string name="unhandled_exception_in_vs">Необработанное исключение в VS</string>
<string name="send_exception_to_developers">Отправить исключение разработчикам…</string>
<string name="your_license_key_has_been_set_an_app_restart_might_be_required">Ваш лицензионный ключ установлен!\nМожет потребоваться перезагрузка приложения.</string>
<string name="qr_code_too_large_use_text_below">QR-код слишком большой. Используйте текст ниже, чтобы поделиться своим профилем.</string>
<string name="invalid_license_format">Неверный формат лицензии</string>
<string name="unknown_content_format">Неизвестный формат содержимого</string>
<string name="unknown_file_format">Неизвестный формат файла</string>
+1
View File
@@ -581,6 +581,7 @@
<string name="unhandled_exception_in_vs">VS\'de bilinmeyen hata (exception)</string>
<string name="send_exception_to_developers">Geliştiricilere exception\'ı gönder…</string>
<string name="your_license_key_has_been_set_an_app_restart_might_be_required">Lisans anahtarınız ayarlandı!\nUygulamayı yeniden başlatmanız gerekebilir.</string>
<string name="qr_code_too_large_use_text_below">QR kodu çok büyük. Profilinizi paylaşmak için aşağıdaki metni kullanın.</string>
<string name="invalid_license_format">Geçersiz lisans formatı</string>
<string name="unknown_content_format">Bilinmeyen içerik formatı</string>
<string name="unknown_file_format">Bilinmeyen dosya formatı</string>
+1
View File
@@ -411,6 +411,7 @@
<string name="unhandled_exception_in_vs">VS 中的未处理异常</string>
<string name="send_exception_to_developers">向开发者发送异常…</string>
<string name="your_license_key_has_been_set_an_app_restart_might_be_required">您的许可证密钥已设置!\n可能需要重新启动应用程序。</string>
<string name="qr_code_too_large_use_text_below">二维码太大。请使用下方文字分享您的个人资料。</string>
<string name="invalid_license_format">无效的许可证格式</string>
<string name="unknown_content_format">未知的内容格式</string>
<string name="unknown_file_format">未知的文件格式</string>
+1
View File
@@ -643,6 +643,7 @@
<string name="unhandled_exception_in_vs">Unhandled exception in VS</string>
<string name="send_exception_to_developers">Send exception to developers…</string>
<string name="your_license_key_has_been_set_an_app_restart_might_be_required">Your license key has been set!\nAn app restart might be required.</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="invalid_license_format">Invalid license format</string>
<string name="unknown_content_format">Unknown content format</string>
<string name="unknown_file_format">Unknown file format</string>