Skip to content

Commit a42bb41

Browse files
committed
fix(reviewers): support localStorage again
After the migration to use a server that can handle the backend POST requests, AnkiDroid moved from having a static base URL to a dynamic one because the server port could change. This fixes the issue by using a default port number. That should work in 99% of the cases. If by chance another app uses the same port, a free one will be used instead. I haven't tested, but this may happen with parallel builds of AnkiDroid running at the same time. Still, the only issue that would surge is that localStorage wouldn't work on one of open builds until the other one is closed
1 parent a19cb96 commit a42bb41

File tree

4 files changed

+39
-4
lines changed

4 files changed

+39
-4
lines changed

AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ import com.ichi2.anki.settings.Prefs
128128
import com.ichi2.anki.snackbar.BaseSnackbarBuilderProvider
129129
import com.ichi2.anki.snackbar.SnackbarBuilder
130130
import com.ichi2.anki.snackbar.showSnackbar
131+
import com.ichi2.anki.ui.windows.reviewer.ReviewerViewModel
131132
import com.ichi2.anki.utils.OnlyOnce.Method.ANSWER_CARD
132133
import com.ichi2.anki.utils.OnlyOnce.preventSimultaneousExecutions
133134
import com.ichi2.anki.utils.ext.showDialogFragment
@@ -163,6 +164,8 @@ import kotlinx.coroutines.runBlocking
163164
import timber.log.Timber
164165
import java.io.File
165166
import java.io.UnsupportedEncodingException
167+
import java.net.BindException
168+
import java.net.ServerSocket
166169
import java.net.URLDecoder
167170
import java.util.concurrent.locks.Lock
168171
import java.util.concurrent.locks.ReadWriteLock
@@ -273,7 +276,7 @@ abstract class AbstractFlashcardViewer :
273276
/** Handle joysticks/pedals */
274277
protected lateinit var motionEventHandler: MotionEventHandler
275278

276-
val server = AnkiServer(this).also { it.start() }
279+
val server: AnkiServer
277280

278281
@get:VisibleForTesting
279282
var cardContent: String? = null
@@ -367,6 +370,16 @@ abstract class AbstractFlashcardViewer :
367370
}
368371

369372
init {
373+
val port =
374+
try {
375+
ServerSocket(ReviewerViewModel.DEFAULT_PORT).use {
376+
it.reuseAddress = true
377+
it.localPort
378+
}
379+
} catch (_: BindException) {
380+
0
381+
}
382+
server = AnkiServer(this, port).also { it.start() }
370383
ChangeManager.subscribe(this)
371384
}
372385

AnkiDroid/src/main/java/com/ichi2/anki/pages/AnkiServer.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import java.io.ByteArrayInputStream
2424

2525
open class AnkiServer(
2626
private val postHandler: PostRequestHandler,
27-
) : NanoHTTPD(LOCALHOST, 0) {
27+
port: Int = 0,
28+
) : NanoHTTPD(LOCALHOST, port) {
2829
fun baseUrl(): String = "http://$LOCALHOST:$listeningPort/"
2930

3031
// it's faster to serve local files without GZip. see 'page render' in logs

AnkiDroid/src/main/java/com/ichi2/anki/previewer/CardViewerViewModel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ abstract class CardViewerViewModel(
6767

6868
@CallSuper
6969
override fun onCleared() {
70-
super.onCleared()
7170
cardMediaPlayer.close()
71+
server.stop()
72+
super.onCleared()
7273
}
7374

7475
/* *********************************************************************************************

AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerViewModel.kt

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ import kotlinx.coroutines.delay
6868
import kotlinx.coroutines.flow.MutableSharedFlow
6969
import kotlinx.coroutines.flow.MutableStateFlow
7070
import timber.log.Timber
71+
import java.net.BindException
72+
import java.net.ServerSocket
7173

7274
class ReviewerViewModel(
7375
cardMediaPlayer: CardMediaPlayer,
@@ -96,7 +98,7 @@ class ReviewerViewModel(
9698
val typeAnswerFlow = MutableStateFlow<TypeAnswer?>(null)
9799
val destinationFlow = MutableSharedFlow<Destination>()
98100

99-
override val server = AnkiServer(this).also { it.start() }
101+
override val server: AnkiServer
100102
private val stateMutationKey = TimeManager.time.intTimeMS().toString()
101103
val statesMutationEval = MutableSharedFlow<String>()
102104

@@ -124,6 +126,17 @@ class ReviewerViewModel(
124126
}
125127

126128
init {
129+
val port =
130+
try {
131+
ServerSocket(DEFAULT_PORT).use {
132+
it.reuseAddress = true
133+
it.localPort
134+
}
135+
} catch (_: BindException) {
136+
0
137+
}
138+
server = AnkiServer(this, port).also { it.start() }
139+
127140
keyMap.setProcessor(this)
128141
ChangeManager.subscribe(this)
129142
launchCatchingIO {
@@ -628,6 +641,13 @@ class ReviewerViewModel(
628641
}
629642

630643
companion object {
644+
/**
645+
* Default port of the Reviewer's [AnkiServer].
646+
* Using a static port makes the URL constant, and that
647+
* makes possible to use JavaScript's `localStorage`.
648+
*/
649+
const val DEFAULT_PORT = 64009
650+
631651
fun factory(
632652
soundPlayer: CardMediaPlayer,
633653
keyMap: PeripheralKeymap<ReviewerBinding, ViewerAction>,

0 commit comments

Comments
 (0)