-
Notifications
You must be signed in to change notification settings - Fork 212
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Base for push notification system using websockets #426
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package suwayomi.tachidesk.event | ||
|
||
import suwayomi.tachidesk.event.enums.EventType | ||
import java.util.UUID | ||
|
||
data class Event<T>( | ||
val id: UUID? = UUID.randomUUID(), | ||
val type: EventType, | ||
val entity: T | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package suwayomi.tachidesk.event | ||
|
||
import io.javalin.websocket.WsContext | ||
import io.javalin.websocket.WsMessageContext | ||
import java.util.concurrent.ConcurrentHashMap | ||
import java.util.concurrent.CopyOnWriteArrayList | ||
|
||
abstract class EventDispatcher<T : EventEntity> { | ||
private val clients = ConcurrentHashMap<String, WsContext>() | ||
private val eventQueue = CopyOnWriteArrayList<Event<T>>() | ||
|
||
fun addClient(ctx: WsContext) { | ||
clients[ctx.sessionId] = ctx | ||
} | ||
|
||
fun removeClient(ctx: WsContext) { | ||
clients.remove(key = ctx.sessionId) | ||
} | ||
|
||
abstract fun notifyClient(ctx: WsContext, event: Event<T>) | ||
|
||
abstract fun handleRequest(ctx: WsMessageContext) | ||
|
||
fun notifyAllClients(event: Event<T>) { | ||
clients.forEach { | ||
notifyClient(ctx = it.value, event = event) | ||
} | ||
} | ||
|
||
fun enqueue(event: Event<T>) { | ||
eventQueue += event | ||
} | ||
|
||
// to be consumed in the client based on | ||
// the event type in a notifications screen | ||
fun queue() = eventQueue | ||
|
||
fun dequeue(event: Event<T>) { | ||
eventQueue.removeIf { it.id == event.id } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package suwayomi.tachidesk.event | ||
|
||
abstract class EventEntity |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package suwayomi.tachidesk.event.enums | ||
|
||
enum class EventType { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would be some example events for these types? |
||
SYSTEM, | ||
INFORMATIVE, | ||
STATIC, | ||
DYNAMIC | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package suwayomi.tachidesk.manga.impl.download | ||
|
||
import io.javalin.websocket.WsContext | ||
import io.javalin.websocket.WsMessageContext | ||
import suwayomi.tachidesk.event.Event | ||
import suwayomi.tachidesk.event.EventDispatcher | ||
import suwayomi.tachidesk.manga.impl.download.model.DownloadStatus | ||
|
||
class DownloadEventDispatcher : EventDispatcher<DownloadStatus>() { | ||
override fun notifyClient(ctx: WsContext, event: Event<DownloadStatus>) { | ||
ctx.send( | ||
event.entity | ||
) | ||
} | ||
|
||
override fun handleRequest(ctx: WsMessageContext) { | ||
when (ctx.message()) { | ||
"STATUS" -> DownloadManager.notifyClient(ctx) | ||
else -> ctx.send( | ||
""" | ||
|Invalid command. | ||
|Supported commands are: | ||
| - STATUS | ||
| sends the current download status | ||
| | ||
""".trimMargin() | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,62 +8,30 @@ package suwayomi.tachidesk.manga.impl.download | |
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ | ||
|
||
import io.javalin.websocket.WsContext | ||
import io.javalin.websocket.WsMessageContext | ||
import org.jetbrains.exposed.sql.and | ||
import org.jetbrains.exposed.sql.select | ||
import org.jetbrains.exposed.sql.transactions.transaction | ||
import suwayomi.tachidesk.event.Event | ||
import suwayomi.tachidesk.event.enums.EventType | ||
import suwayomi.tachidesk.manga.impl.Manga.getManga | ||
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter | ||
import suwayomi.tachidesk.manga.impl.download.model.DownloadState.Downloading | ||
import suwayomi.tachidesk.manga.impl.download.model.DownloadStatus | ||
import suwayomi.tachidesk.manga.model.table.ChapterTable | ||
import suwayomi.tachidesk.manga.model.table.toDataClass | ||
import java.util.concurrent.ConcurrentHashMap | ||
import java.util.concurrent.CopyOnWriteArrayList | ||
|
||
object DownloadManager { | ||
private val clients = ConcurrentHashMap<String, WsContext>() | ||
val eventDispatcher = DownloadEventDispatcher() | ||
private val downloadQueue = CopyOnWriteArrayList<DownloadChapter>() | ||
private var downloader: Downloader? = null | ||
|
||
fun addClient(ctx: WsContext) { | ||
clients[ctx.sessionId] = ctx | ||
} | ||
|
||
fun removeClient(ctx: WsContext) { | ||
clients.remove(ctx.sessionId) | ||
} | ||
|
||
fun notifyClient(ctx: WsContext) { | ||
ctx.send( | ||
getStatus() | ||
) | ||
} | ||
|
||
fun handleRequest(ctx: WsMessageContext) { | ||
when (ctx.message()) { | ||
"STATUS" -> notifyClient(ctx) | ||
else -> ctx.send( | ||
""" | ||
|Invalid command. | ||
|Supported commands are: | ||
| - STATUS | ||
| sends the current download status | ||
| | ||
""".trimMargin() | ||
) | ||
} | ||
} | ||
|
||
private fun notifyAllClients() { | ||
val status = getStatus() | ||
clients.forEach { | ||
it.value.send(status) | ||
} | ||
eventDispatcher.notifyClient(ctx, getStatus()) | ||
} | ||
|
||
private fun getStatus(): DownloadStatus { | ||
return DownloadStatus( | ||
private fun getStatus(): Event<DownloadStatus> { | ||
val status = DownloadStatus( | ||
if (downloader == null || | ||
downloadQueue.none { it.state == Downloading } | ||
) { | ||
|
@@ -73,6 +41,10 @@ object DownloadManager { | |
}, | ||
downloadQueue | ||
) | ||
return Event( | ||
type = EventType.STATIC, | ||
entity = status | ||
) | ||
} | ||
|
||
suspend fun enqueue(chapterIndex: Int, mangaId: Int) { | ||
|
@@ -92,12 +64,16 @@ object DownloadManager { | |
) | ||
start() | ||
} | ||
notifyAllClients() | ||
val status = getStatus() | ||
eventDispatcher.enqueue(status) | ||
eventDispatcher.notifyAllClients(status) | ||
} | ||
|
||
fun unqueue(chapterIndex: Int, mangaId: Int) { | ||
downloadQueue.removeIf { it.mangaId == mangaId && it.chapterIndex == chapterIndex } | ||
notifyAllClients() | ||
val status = getStatus() | ||
eventDispatcher.dequeue(status) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this would ever do anything. Also, I don't think this unqueue is supposed to dequeue the status. |
||
eventDispatcher.notifyAllClients(status) | ||
} | ||
|
||
fun start() { | ||
|
@@ -107,11 +83,11 @@ object DownloadManager { | |
} | ||
|
||
if (downloader == null) { | ||
downloader = Downloader(downloadQueue) { notifyAllClients() } | ||
downloader = Downloader(downloadQueue) { eventDispatcher.notifyAllClients(getStatus()) } | ||
downloader!!.start() | ||
} | ||
|
||
notifyAllClients() | ||
eventDispatcher.notifyAllClients(getStatus()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could |
||
} | ||
|
||
fun stop() { | ||
|
@@ -121,13 +97,13 @@ object DownloadManager { | |
} | ||
} | ||
downloader = null | ||
notifyAllClients() | ||
eventDispatcher.notifyAllClients(getStatus()) | ||
} | ||
|
||
fun clear() { | ||
stop() | ||
downloadQueue.clear() | ||
notifyAllClients() | ||
eventDispatcher.notifyAllClients(getStatus()) | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what this should be used for. Could you expand on what would be the content of "Notifications screen"?