fixed downloading galleris with long title, [wip] transfer data

This commit is contained in:
tom5079
2024-04-17 23:25:36 -07:00
parent b0e194898e
commit 68ec919ae4
11 changed files with 209 additions and 38 deletions

View File

@@ -38,7 +38,7 @@ android {
compileSdk 34
targetSdkVersion 34
versionCode 69
versionName "5.3.13"
versionName "5.3.14"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}

View File

@@ -12,7 +12,7 @@
"filters": [],
"attributes": [],
"versionCode": 69,
"versionName": "5.3.12",
"versionName": "5.3.14",
"outputFile": "app-release.apk"
}
],

View File

@@ -343,7 +343,7 @@ class DownloadService : Service() {
return@launch
}
notification[galleryID]?.setContentTitle(galleryInfo.title?.ellipsize(30))
notification[galleryID]?.setContentTitle(galleryInfo.title.ellipsize(32))
notify(galleryID)
val queued = mutableSetOf<Int>()
@@ -408,7 +408,7 @@ class DownloadService : Service() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
startForeground(R.id.downloader_notification_id, serviceNotification.build())
} else {
startForeground(R.id.downloader_notification_id, serviceNotification.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
startForeground(R.id.downloader_notification_id, serviceNotification.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
}
when (intent?.getStringExtra(KEY_COMMAND)) {
@@ -433,7 +433,7 @@ class DownloadService : Service() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
startForeground(R.id.downloader_notification_id, serviceNotification.build())
} else {
startForeground(R.id.downloader_notification_id, serviceNotification.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
startForeground(R.id.downloader_notification_id, serviceNotification.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
}
interceptors[Tag::class] = interceptor
}

View File

@@ -12,18 +12,21 @@ import io.ktor.network.selector.SelectorManager
import io.ktor.network.sockets.aSocket
import io.ktor.network.sockets.openReadChannel
import io.ktor.network.sockets.openWriteChannel
import io.ktor.utils.io.writeStringUtf8
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import xyz.quaver.pupil.R
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class TransferClientService : Service() {
private val selectorManager = SelectorManager(Dispatchers.IO)
private val channel = Channel<String>()
private val channel = Channel<Pair<TransferPacket, Continuation<TransferPacket>>>()
private var job: Job? = null
private fun startForeground() = runCatching {
@@ -64,13 +67,28 @@ class TransferClientService : Service() {
val writeChannel = socket.openWriteChannel(autoFlush = true)
runCatching {
TransferPacket.Hello().writeToChannel(writeChannel)
val handshake = TransferPacket.readFromChannel(readChannel)
if (handshake !is TransferPacket.Hello || handshake.version != TRANSFER_PROTOCOL_VERSION) {
throw IllegalStateException("Invalid handshake")
}
while (true) {
val message = channel.receive()
Log.d("PUPILD", "Sending message $message!")
writeChannel.writeStringUtf8(message)
Log.d("PUPILD", readChannel.readUTF8Line(4).toString())
val (packet, continuation) = channel.receive()
Log.d("PUPILD", "Sending packet $packet")
packet.writeToChannel(writeChannel)
val response = TransferPacket.readFromChannel(readChannel).also {
Log.d("PUPILD", "Received packet $it")
}
continuation.resume(response)
}
}.onFailure {
Log.d("PUPILD", "Connection closed with error $it")
socket.close()
stopSelf(startId)
}
@@ -85,9 +103,14 @@ class TransferClientService : Service() {
}
inner class Binder: android.os.Binder() {
fun sendMessage(message: String) {
Log.d("PUPILD", "Sending message $message")
channel.trySendBlocking(message + '\n')
suspend fun sendPacket(packet: TransferPacket): Result<TransferPacket.ListResponse> = runCatching {
val response = withTimeout(1000) { suspendCoroutine { continuation ->
channel.trySendBlocking(packet to continuation)
} }
response as? TransferPacket.ListResponse ?: throw IllegalStateException("Invalid response")
}
}

View File

@@ -0,0 +1,94 @@
package xyz.quaver.pupil.services
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.ByteWriteChannel
const val TRANSFER_PROTOCOL_VERSION: UByte = 1u
enum class TransferType(val value: UByte) {
INVALID(255u),
HELLO(0u),
PING(1u),
PONG(2u),
LIST_REQUEST(3u),
LIST_RESPONSE(4u),
}
sealed interface TransferPacket {
val type: TransferType
suspend fun writeToChannel(channel: ByteWriteChannel)
data class Hello(val version: UByte = TRANSFER_PROTOCOL_VERSION): TransferPacket {
override val type = TransferType.HELLO
override suspend fun writeToChannel(channel: ByteWriteChannel) {
channel.writeByte(type.value.toByte())
channel.writeByte(version.toByte())
}
}
data object Ping: TransferPacket {
override val type = TransferType.PING
override suspend fun writeToChannel(channel: ByteWriteChannel) {
channel.writeByte(type.value.toByte())
}
}
data object Pong: TransferPacket {
override val type = TransferType.PONG
override suspend fun writeToChannel(channel: ByteWriteChannel) {
channel.writeByte(type.value.toByte())
}
}
data object ListRequest: TransferPacket {
override val type = TransferType.LIST_REQUEST
override suspend fun writeToChannel(channel: ByteWriteChannel) {
channel.writeByte(type.value.toByte())
}
}
data object Invalid: TransferPacket {
override val type = TransferType.INVALID
override suspend fun writeToChannel(channel: ByteWriteChannel) {
channel.writeByte(type.value.toByte())
}
}
data class ListResponse(
val favoritesCount: Int,
val historyCount: Int,
val downloadsCount: Int,
): TransferPacket {
override val type = TransferType.LIST_RESPONSE
override suspend fun writeToChannel(channel: ByteWriteChannel) {
channel.writeByte(type.value.toByte())
channel.writeInt(favoritesCount)
channel.writeInt(historyCount)
channel.writeInt(downloadsCount)
}
}
companion object {
suspend fun readFromChannel(channel: ByteReadChannel): TransferPacket {
return when(val type = channel.readByte().toUByte()) {
TransferType.HELLO.value -> {
val version = channel.readByte().toUByte()
Hello(version)
}
TransferType.PING.value -> Ping
TransferType.PONG.value -> Pong
TransferType.LIST_REQUEST.value -> ListRequest
TransferType.LIST_RESPONSE.value -> {
val favoritesCount = channel.readInt()
val historyCount = channel.readInt()
val downloadsCount = channel.readInt()
ListResponse(favoritesCount, historyCount, downloadsCount)
}
else -> throw IllegalArgumentException("Unknown packet type: $type")
}
}
}
}

View File

@@ -14,12 +14,15 @@ import io.ktor.network.sockets.Socket
import io.ktor.network.sockets.aSocket
import io.ktor.network.sockets.openReadChannel
import io.ktor.network.sockets.openWriteChannel
import io.ktor.utils.io.writeStringUtf8
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import xyz.quaver.pupil.R
import xyz.quaver.pupil.favorites
import xyz.quaver.pupil.histories
import xyz.quaver.pupil.util.downloader.DownloadManager
class TransferServerService : Service() {
private val selectorManager = SelectorManager(Dispatchers.IO)
@@ -43,15 +46,33 @@ class TransferServerService : Service() {
)
}
private fun generateListResponse(): TransferPacket.ListResponse {
val favoritesCount = favorites.size
val historyCount = histories.size
val downloadsCount = DownloadManager.getInstance(this).downloadFolderMap.size
return TransferPacket.ListResponse(favoritesCount, historyCount, downloadsCount)
}
private suspend fun handleConnection(socket: Socket) {
val readChannel = socket.openReadChannel()
val writeChannel = socket.openWriteChannel(autoFlush = true)
runCatching {
while (true) {
if (readChannel.readUTF8Line(8) == "ping") {
writeChannel.writeStringUtf8("pong\n")
val packet = TransferPacket.readFromChannel(readChannel)
Log.d("PUPILD", "Received packet $packet")
binder.channel.trySend(packet)
val response = when (packet) {
is TransferPacket.Hello -> TransferPacket.Hello()
is TransferPacket.Ping -> TransferPacket.Pong
is TransferPacket.ListRequest -> generateListResponse()
else -> TransferPacket.Invalid
}
response.writeToChannel(writeChannel)
}
}.onFailure {
socket.close()
@@ -93,7 +114,7 @@ class TransferServerService : Service() {
}
inner class Binder: android.os.Binder() {
fun getService() = this@TransferServerService
val channel = Channel<TransferPacket>()
}
private val binder = Binder()

View File

@@ -36,7 +36,6 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
@@ -44,6 +43,7 @@ import kotlinx.coroutines.launch
import xyz.quaver.pupil.R
import xyz.quaver.pupil.receiver.WifiDirectBroadcastReceiver
import xyz.quaver.pupil.services.TransferClientService
import xyz.quaver.pupil.services.TransferPacket
import xyz.quaver.pupil.services.TransferServerService
import xyz.quaver.pupil.ui.fragment.TransferConnectedFragment
import xyz.quaver.pupil.ui.fragment.TransferDirectionFragment
@@ -81,15 +81,15 @@ class TransferActivity : AppCompatActivity(R.layout.transfer_activity) {
}
}
private var serviceBinder: TransferClientService.Binder? = null
private var clientServiceBinder: TransferClientService.Binder? = null
private val serviceConnection = object : ServiceConnection {
private val clientServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
serviceBinder = service as TransferClientService.Binder
clientServiceBinder = service as TransferClientService.Binder
}
override fun onServiceDisconnected(name: ComponentName?) {
serviceBinder = null
clientServiceBinder = null
}
}
@@ -118,6 +118,17 @@ class TransferActivity : AppCompatActivity(R.layout.transfer_activity) {
return true
}
private fun handleServerResponse(response: TransferPacket?) {
when (response) {
is TransferPacket.ListResponse -> {
Log.d("PUPILD", "Received list response $response")
}
else -> {
Log.d("PUPILD", "Received invalid response $response")
}
}
}
@SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -146,11 +157,12 @@ class TransferActivity : AppCompatActivity(R.layout.transfer_activity) {
}
lifecycleScope.launch {
viewModel.messageQueue.consumeEach {
serviceBinder?.sendMessage(it)
clientServiceBinder?.sendPacket(it)?.getOrNull()?.let(::handleServerResponse)
}
}
viewModel.step.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).cancellable().collect step@{ step ->
Log.d("PUPILD", "Step: $step")
lifecycleScope.launch {
viewModel.step.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collectLatest step@{ step ->
when (step) {
TransferStep.TARGET,
TransferStep.TARGET_FORCE -> {
@@ -176,7 +188,8 @@ class TransferActivity : AppCompatActivity(R.layout.transfer_activity) {
val intent = Intent(this@TransferActivity, TransferClientService::class.java).also {
it.putExtra("address", hostAddress)
}
bindService(intent, serviceConnection, BIND_AUTO_CREATE)
ContextCompat.startForegroundService(this@TransferActivity, intent)
bindService(intent, clientServiceConnection, BIND_AUTO_CREATE)
viewModel.setStep(TransferStep.SELECT_DATA)
}
@@ -243,6 +256,16 @@ class TransferActivity : AppCompatActivity(R.layout.transfer_activity) {
it.putExtra("address", address)
}
ContextCompat.startForegroundService(this@TransferActivity, intent)
val binder: TransferServerService.Binder = suspendCoroutine { continuation ->
bindService(intent, object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
continuation.resume(service as TransferServerService.Binder)
}
override fun onServiceDisconnected(name: ComponentName?) { }
}, BIND_AUTO_CREATE)
}
binder.channel.receive()
viewModel.setStep(TransferStep.CONNECTED)
}.onFailure {
@@ -271,6 +294,7 @@ class TransferActivity : AppCompatActivity(R.layout.transfer_activity) {
override fun onResume() {
super.onResume()
bindService(Intent(this, TransferClientService::class.java), clientServiceConnection, BIND_AUTO_CREATE)
WifiDirectBroadcastReceiver(manager, channel, viewModel).also {
receiver = it
registerReceiver(it, intentFilter)
@@ -279,6 +303,7 @@ class TransferActivity : AppCompatActivity(R.layout.transfer_activity) {
override fun onPause() {
super.onPause()
unbindService(clientServiceConnection)
receiver?.let { unregisterReceiver(it) }
receiver = null
}
@@ -313,7 +338,7 @@ class TransferViewModel : ViewModel() {
private val _peerToConnect: MutableLiveData<WifiP2pDevice?> = MutableLiveData(null)
val peerToConnect: LiveData<WifiP2pDevice?> = _peerToConnect
val messageQueue: Channel<String> = Channel()
val messageQueue: Channel<TransferPacket> = Channel()
fun setStep(step: TransferStep) {
Log.d("PUPILD", "Set step: $step")
@@ -345,6 +370,10 @@ class TransferViewModel : ViewModel() {
}
fun ping() {
messageQueue.trySend("ping")
messageQueue.trySend(TransferPacket.Ping)
}
fun list() {
messageQueue.trySend(TransferPacket.ListRequest)
}
}

View File

@@ -24,7 +24,7 @@ class TransferSelectDataFragment: Fragment() {
_binding = TransferSelectDataFragmentBinding.inflate(inflater, container, false)
binding.checkAll.setOnCheckedChangeListener { _, isChecked ->
viewModel.ping()
viewModel.list()
}
return binding.root

View File

@@ -20,6 +20,7 @@ package xyz.quaver.pupil.util.downloader
import android.content.Context
import android.content.ContextWrapper
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

View File

@@ -24,6 +24,7 @@ import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
@@ -136,18 +137,20 @@ fun byteCount(codePoint: Int): Int = when (codePoint) {
fun String.ellipsize(n: Int): String = buildString {
var count = 0
var index = 0
val codePointLength = this.codePointCount(0, this.length)
val codePointLength = this@ellipsize.codePointCount(0, this@ellipsize.length)
while (index < codePointLength) {
val next = this.codePointAt(index)
if (count + next > 124) {
val nextCodePoint = this@ellipsize.codePointAt(index)
val nextByte = byteCount(nextCodePoint)
if (count + nextByte > 124) {
append("")
break
}
appendCodePoint(next)
count += next
appendCodePoint(nextCodePoint)
count += nextByte
index++
}
}
operator fun JsonElement.get(index: Int) =

View File

@@ -52,9 +52,9 @@
app:defaultValue="8"
app:useSimpleSummaryProvider="true"/>
<Preference
app:key="transfer_data"
app:title="@string/settings_transfer_data"/>
<!-- <Preference-->
<!-- app:key="transfer_data"-->
<!-- app:title="@string/settings_transfer_data"/>-->
<SwitchPreferenceCompat
app:key="nomedia"