fixed downloading galleris with long title, [wip] transfer data
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 69,
|
||||
"versionName": "5.3.12",
|
||||
"versionName": "5.3.14",
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class TransferSelectDataFragment: Fragment() {
|
||||
_binding = TransferSelectDataFragmentBinding.inflate(inflater, container, false)
|
||||
|
||||
binding.checkAll.setOnCheckedChangeListener { _, isChecked ->
|
||||
viewModel.ping()
|
||||
viewModel.list()
|
||||
}
|
||||
|
||||
return binding.root
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) =
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user