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:nc_photos/account.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/cache_manager_util.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/help_utils.dart';
@ -64,10 +62,15 @@ class EnhanceHandler {
return;
}
_log.info("[call] Selected: ${selected.name}");
final imageUri = await _getFileUri();
switch (selected) {
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;
}
}
@ -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 File file;

View file

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

View file

@ -1,3 +1,5 @@
package com.nkming.nc_photos.plugin
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" -> {
try {
zeroDce(
call.argument("image")!!,
call.argument("fileUrl")!!,
call.argument("headers"),
call.argument("filename")!!,
result
)
@ -42,14 +43,18 @@ class ImageProcessorChannelHandler(context: Context) :
}
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 {
putExtra(
ImageProcessorService.EXTRA_METHOD,
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)
}
ContextCompat.startForegroundService(context, intent)

View file

@ -18,12 +18,16 @@ import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.nkming.nc_photos.plugin.image_processor.ZeroDce
import java.io.File
import java.net.HttpURLConnection
import java.net.URL
class ImageProcessorService : Service() {
companion object {
const val EXTRA_METHOD = "method"
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"
private const val NOTIFICATION_ID =
@ -45,6 +49,7 @@ class ImageProcessorService : Service() {
super.onCreate()
wakeLock.acquire()
createNotificationChannel()
cleanUp()
}
override fun onDestroy() {
@ -55,7 +60,7 @@ class ImageProcessorService : Service() {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
assert(intent.hasExtra(EXTRA_METHOD))
assert(intent.hasExtra(EXTRA_IMAGE))
assert(intent.hasExtra(EXTRA_FILE_URL))
if (!isForeground) {
try {
startForeground(NOTIFICATION_ID, buildNotification())
@ -73,19 +78,23 @@ class ImageProcessorService : Service() {
Log.e(TAG, "Unknown method: $method")
// we can't call stopSelf here as it'll stop the service even if
// there are commands running in the bg
addCommand(
ImageProcessorCommand(startId, "null", Uri.EMPTY, "")
)
addCommand(ImageProcessorCommand(startId, "null", "", null, ""))
}
}
return START_REDELIVER_INTENT
}
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)!!
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 val cmds = mutableListOf<ImageProcessorCommand>()
private var cmdTask: ImageProcessorCommandTask? = null
@ -205,7 +225,8 @@ class ImageProcessorService : Service() {
private data class ImageProcessorCommand(
val startId: Int,
val method: String,
val uri: Uri,
val fileUrl: String,
val headers: Map<String, String>?,
val filename: String,
val args: Map<String, Any> = mapOf(),
)
@ -222,18 +243,62 @@ private open class ImageProcessorCommandTask(context: Context) :
): MessageEvent {
val cmd = params[0]!!
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) {
ImageProcessorService.METHOD_ZERO_DCE -> ZeroDce(context).infer(
cmd.uri
fileUri
)
else -> throw IllegalArgumentException(
"Unknown method: ${cmd.method}"
)
}
val uri = saveBitmap(output, cmd.filename)
ImageProcessorCompletedEvent(cmd.uri, uri)
} catch (e: Throwable) {
ImageProcessorFailedEvent(cmd.uri, e)
saveBitmap(output, cmd.filename)
} finally {
file.delete()
}
}
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")
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.os.Build
import java.net.HttpURLConnection
fun getPendingIntentFlagImmutable(): Int {
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)
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;
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>{
"image": image,
"fileUrl": fileUrl,
"headers": headers,
"filename": filename,
});