Enhance original file instead of cached preview

This commit is contained in:
Ming Ming 2022-05-09 22:51:37 +08:00
parent 192fe923a2
commit 6d0f612c7b
7 changed files with 123 additions and 40 deletions

View file

@ -2,9 +2,7 @@ import 'package:flutter/material.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart'; import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/api/api.dart';
import 'package:nc_photos/api/api_util.dart' as api_util;
import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/cache_manager_util.dart';
import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/help_utils.dart'; import 'package:nc_photos/help_utils.dart';
@ -64,10 +62,15 @@ class EnhanceHandler {
return; return;
} }
_log.info("[call] Selected: ${selected.name}"); _log.info("[call] Selected: ${selected.name}");
final imageUri = await _getFileUri();
switch (selected) { switch (selected) {
case _Algorithm.zeroDce: case _Algorithm.zeroDce:
await ImageProcessor.zeroDce(imageUri.toString(), file.filename); await ImageProcessor.zeroDce(
"${account.url}/${file.path}",
file.filename,
headers: {
"Authorization": Api.getAuthorizationHeaderValue(account),
},
);
break; break;
} }
} }
@ -104,22 +107,6 @@ class EnhanceHandler {
), ),
]; ];
Future<Uri> _getFileUri() async {
final f = await LargeImageCacheManager.inst.getSingleFile(
api_util.getFilePreviewUrl(
account,
file,
width: k.photoLargeSize,
height: k.photoLargeSize,
a: true,
),
headers: {
"Authorization": Api.getAuthorizationHeaderValue(account),
},
);
return f.absolute.uri;
}
final Account account; final Account account;
final File file; final File file;

View file

@ -5,11 +5,9 @@ import android.net.Uri
interface MessageEvent interface MessageEvent
data class ImageProcessorCompletedEvent( data class ImageProcessorCompletedEvent(
val image: Uri,
val result: Uri, val result: Uri,
) : MessageEvent ) : MessageEvent
data class ImageProcessorFailedEvent( data class ImageProcessorFailedEvent(
val image: Uri,
val exception: Throwable, val exception: Throwable,
) : MessageEvent ) : MessageEvent

View file

@ -1,3 +1,5 @@
package com.nkming.nc_photos.plugin package com.nkming.nc_photos.plugin
class PermissionException(message: String) : Exception(message) class PermissionException(message: String) : Exception(message)
class HttpException(statusCode: Int, message: String): Exception(message)

View file

@ -20,7 +20,8 @@ class ImageProcessorChannelHandler(context: Context) :
"zeroDce" -> { "zeroDce" -> {
try { try {
zeroDce( zeroDce(
call.argument("image")!!, call.argument("fileUrl")!!,
call.argument("headers"),
call.argument("filename")!!, call.argument("filename")!!,
result result
) )
@ -42,14 +43,18 @@ class ImageProcessorChannelHandler(context: Context) :
} }
private fun zeroDce( private fun zeroDce(
image: String, filename: String, result: MethodChannel.Result fileUrl: String, headers: Map<String, String>?, filename: String,
result: MethodChannel.Result
) { ) {
val intent = Intent(context, ImageProcessorService::class.java).apply { val intent = Intent(context, ImageProcessorService::class.java).apply {
putExtra( putExtra(
ImageProcessorService.EXTRA_METHOD, ImageProcessorService.EXTRA_METHOD,
ImageProcessorService.METHOD_ZERO_DCE ImageProcessorService.METHOD_ZERO_DCE
) )
putExtra(ImageProcessorService.EXTRA_IMAGE, image) putExtra(ImageProcessorService.EXTRA_FILE_URL, fileUrl)
putExtra(
ImageProcessorService.EXTRA_HEADERS,
headers?.let { HashMap(it) })
putExtra(ImageProcessorService.EXTRA_FILENAME, filename) putExtra(ImageProcessorService.EXTRA_FILENAME, filename)
} }
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)

View file

@ -18,12 +18,16 @@ import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.nkming.nc_photos.plugin.image_processor.ZeroDce import com.nkming.nc_photos.plugin.image_processor.ZeroDce
import java.io.File
import java.net.HttpURLConnection
import java.net.URL
class ImageProcessorService : Service() { class ImageProcessorService : Service() {
companion object { companion object {
const val EXTRA_METHOD = "method" const val EXTRA_METHOD = "method"
const val METHOD_ZERO_DCE = "zero-dce" const val METHOD_ZERO_DCE = "zero-dce"
const val EXTRA_IMAGE = "image" const val EXTRA_FILE_URL = "fileUrl"
const val EXTRA_HEADERS = "headers"
const val EXTRA_FILENAME = "filename" const val EXTRA_FILENAME = "filename"
private const val NOTIFICATION_ID = private const val NOTIFICATION_ID =
@ -45,6 +49,7 @@ class ImageProcessorService : Service() {
super.onCreate() super.onCreate()
wakeLock.acquire() wakeLock.acquire()
createNotificationChannel() createNotificationChannel()
cleanUp()
} }
override fun onDestroy() { override fun onDestroy() {
@ -55,7 +60,7 @@ class ImageProcessorService : Service() {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
assert(intent.hasExtra(EXTRA_METHOD)) assert(intent.hasExtra(EXTRA_METHOD))
assert(intent.hasExtra(EXTRA_IMAGE)) assert(intent.hasExtra(EXTRA_FILE_URL))
if (!isForeground) { if (!isForeground) {
try { try {
startForeground(NOTIFICATION_ID, buildNotification()) startForeground(NOTIFICATION_ID, buildNotification())
@ -73,19 +78,23 @@ class ImageProcessorService : Service() {
Log.e(TAG, "Unknown method: $method") Log.e(TAG, "Unknown method: $method")
// we can't call stopSelf here as it'll stop the service even if // we can't call stopSelf here as it'll stop the service even if
// there are commands running in the bg // there are commands running in the bg
addCommand( addCommand(ImageProcessorCommand(startId, "null", "", null, ""))
ImageProcessorCommand(startId, "null", Uri.EMPTY, "")
)
} }
} }
return START_REDELIVER_INTENT return START_REDELIVER_INTENT
} }
private fun onZeroDce(startId: Int, extras: Bundle) { private fun onZeroDce(startId: Int, extras: Bundle) {
val imageUri = Uri.parse(extras.getString(EXTRA_IMAGE)!!) val fileUrl = extras.getString(EXTRA_FILE_URL)!!
@Suppress("Unchecked_cast")
val headers =
extras.getSerializable(EXTRA_HEADERS) as HashMap<String, String>?
val filename = extras.getString(EXTRA_FILENAME)!! val filename = extras.getString(EXTRA_FILENAME)!!
addCommand( addCommand(
ImageProcessorCommand(startId, METHOD_ZERO_DCE, imageUri, filename) ImageProcessorCommand(
startId, METHOD_ZERO_DCE, fileUrl, headers, filename
)
) )
} }
@ -186,6 +195,17 @@ class ImageProcessorService : Service() {
} }
} }
/**
* Clean up temp files in case the service ended prematurely last time
*/
private fun cleanUp() {
try {
getTempDir(this).deleteRecursively()
} catch (e: Throwable) {
Log.e(TAG, "[cleanUp] Failed while cleanUp", e)
}
}
private var isForeground = false private var isForeground = false
private val cmds = mutableListOf<ImageProcessorCommand>() private val cmds = mutableListOf<ImageProcessorCommand>()
private var cmdTask: ImageProcessorCommandTask? = null private var cmdTask: ImageProcessorCommandTask? = null
@ -205,7 +225,8 @@ class ImageProcessorService : Service() {
private data class ImageProcessorCommand( private data class ImageProcessorCommand(
val startId: Int, val startId: Int,
val method: String, val method: String,
val uri: Uri, val fileUrl: String,
val headers: Map<String, String>?,
val filename: String, val filename: String,
val args: Map<String, Any> = mapOf(), val args: Map<String, Any> = mapOf(),
) )
@ -222,18 +243,62 @@ private open class ImageProcessorCommandTask(context: Context) :
): MessageEvent { ): MessageEvent {
val cmd = params[0]!! val cmd = params[0]!!
return try { return try {
val outUri = handleCommand(cmd)
ImageProcessorCompletedEvent(outUri)
} catch (e: Throwable) {
Log.e(TAG, "[doInBackground] Failed while handleCommand", e)
ImageProcessorFailedEvent(e)
}
}
private fun handleCommand(cmd: ImageProcessorCommand): Uri {
val file = downloadFile(cmd.fileUrl, cmd.headers)
return try {
val fileUri = Uri.fromFile(file)
val output = when (cmd.method) { val output = when (cmd.method) {
ImageProcessorService.METHOD_ZERO_DCE -> ZeroDce(context).infer( ImageProcessorService.METHOD_ZERO_DCE -> ZeroDce(context).infer(
cmd.uri fileUri
) )
else -> throw IllegalArgumentException( else -> throw IllegalArgumentException(
"Unknown method: ${cmd.method}" "Unknown method: ${cmd.method}"
) )
} }
val uri = saveBitmap(output, cmd.filename) saveBitmap(output, cmd.filename)
ImageProcessorCompletedEvent(cmd.uri, uri) } finally {
} catch (e: Throwable) { file.delete()
ImageProcessorFailedEvent(cmd.uri, e) }
}
private fun downloadFile(
fileUrl: String, headers: Map<String, String>?
): File {
Log.i(TAG, "[downloadFile] $fileUrl")
return (URL(fileUrl).openConnection() as HttpURLConnection).apply {
requestMethod = "GET"
instanceFollowRedirects = true
connectTimeout = 8000
readTimeout = 15000
for (entry in (headers ?: mapOf()).entries) {
setRequestProperty(entry.key, entry.value)
}
}.use {
val responseCode = it.responseCode
if (responseCode / 100 == 2) {
val file =
File.createTempFile("img", null, getTempDir(context))
file.outputStream().use { oStream ->
it.inputStream.copyTo(oStream)
}
file
} else {
Log.e(
TAG,
"[downloadFile] Failed downloading file: HTTP$responseCode"
)
throw HttpException(
responseCode, "Failed downloading file (HTTP$responseCode)"
)
}
} }
} }
@ -248,3 +313,14 @@ private open class ImageProcessorCommandTask(context: Context) :
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
private val context = context private val context = context
} }
private fun getTempDir(context: Context): File {
val f = File(context.cacheDir, "imageProcessor")
if (!f.exists()) {
f.mkdirs()
} else if (!f.isDirectory) {
f.delete()
f.mkdirs()
}
return f
}

View file

@ -2,6 +2,7 @@ package com.nkming.nc_photos.plugin
import android.app.PendingIntent import android.app.PendingIntent
import android.os.Build import android.os.Build
import java.net.HttpURLConnection
fun getPendingIntentFlagImmutable(): Int { fun getPendingIntentFlagImmutable(): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
@ -12,3 +13,12 @@ fun getPendingIntentFlagMutable(): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
PendingIntent.FLAG_MUTABLE else 0 PendingIntent.FLAG_MUTABLE else 0
} }
inline fun <T> HttpURLConnection.use(block: (HttpURLConnection) -> T): T {
try {
connect()
return block(this)
} finally {
disconnect()
}
}

View file

@ -4,9 +4,14 @@ import 'package:flutter/services.dart';
import 'package:nc_photos_plugin/src/k.dart' as k; import 'package:nc_photos_plugin/src/k.dart' as k;
class ImageProcessor { class ImageProcessor {
static Future<void> zeroDce(String image, String filename) => static Future<void> zeroDce(
String fileUrl,
String filename, {
Map<String, String>? headers,
}) =>
_methodChannel.invokeMethod("zeroDce", <String, dynamic>{ _methodChannel.invokeMethod("zeroDce", <String, dynamic>{
"image": image, "fileUrl": fileUrl,
"headers": headers,
"filename": filename, "filename": filename,
}); });