diff --git a/mailbox-android/build.gradle b/mailbox-android/build.gradle index 323ed59f4861aa1e5b7837f55b3476e9779b806a..e4868ed5433f1be1acc8ab0076ce6b49abb0870d 100644 --- a/mailbox-android/build.gradle +++ b/mailbox-android/build.gradle @@ -57,9 +57,10 @@ dependencies { implementation "androidx.activity:activity-ktx:1.3.1" implementation "androidx.fragment:fragment-ktx:1.3.6" - def lifecycle_version = "2.3.1" + def lifecycle_version = "2.4.0-alpha03" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxApplication.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxApplication.kt index a8788fa9413f743ae53caff854effe8d6f0c5735..fcc52e4bb31b5d0bd926b045235cdfe80b2b1a56 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxApplication.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxApplication.kt @@ -15,9 +15,4 @@ class MailboxApplication : MultiDexApplication() { @Inject internal lateinit var androidEagerSingletons: AndroidEagerSingletons - override fun onCreate() { - super.onCreate() - MailboxService.startService(this) - } - } diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt index 57c2f8260b592e0c74ce899bfe6ff4be4d9e3d8c..8ff1823fb288124e5a52df349298d701c5bcd04b 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt @@ -1,8 +1,10 @@ package org.briarproject.mailbox.android import android.app.Service +import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.os.IBinder import androidx.core.content.ContextCompat import dagger.hilt.android.AndroidEntryPoint @@ -13,8 +15,10 @@ import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SUCCESS import org.slf4j.LoggerFactory.getLogger +import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject +import kotlin.system.exitProcess @AndroidEntryPoint class MailboxService : Service() { @@ -33,8 +37,11 @@ class MailboxService : Service() { } } + private val created = AtomicBoolean(false) + @Volatile internal var started = false + private var receiver: BroadcastReceiver? = null @Inject internal lateinit var wakeLockManager: AndroidWakeLockManager @@ -48,24 +55,48 @@ class MailboxService : Service() { override fun onCreate() { super.onCreate() + LOG.info("Created") + if (created.getAndSet(true)) { + LOG.warn("Already created") + // FIXME when can this happen? Next line will kill app + stopSelf() + return + } + // Hold a wake lock during startup wakeLockManager.runWakefully({ startForeground(NOTIFICATION_MAIN_ID, notificationManager.serviceNotification) // Start the services in a background thread wakeLockManager.executeWakefully({ val result: StartResult = lifecycleManager.startServices() - if (result === SUCCESS) { - started = true - } else if (result === ALREADY_RUNNING) { - LOG.info("Already running") - stopSelf() - } else { - if (LOG.isWarnEnabled) LOG.warn("Startup failed: $result") - // TODO: implement this - // showStartupFailure(result) - stopSelf() + when { + result === SUCCESS -> started = true + result === ALREADY_RUNNING -> { + LOG.warn("Already running") + // FIXME when can this happen? Next line will kill app + stopSelf() + } + else -> { + if (LOG.isWarnEnabled) LOG.warn("Startup failed: $result") + // TODO: implement this + // and start activity in new process, so we can kill this one + // showStartupFailure(result) + stopSelf() + } } }, "LifecycleStartup") + // Register for device shutdown broadcasts + receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + LOG.info("Device is shutting down") + stopSelf() + } + } + val filter = IntentFilter() + filter.addAction(Intent.ACTION_SHUTDOWN) + filter.addAction("android.intent.action.QUICKBOOT_POWEROFF") + filter.addAction("com.htc.intent.action.QUICKBOOT_POWEROFF") + registerReceiver(receiver, filter) }, "LifecycleStartup") } @@ -76,10 +107,21 @@ class MailboxService : Service() { override fun onDestroy() { wakeLockManager.runWakefully({ super.onDestroy() - wakeLockManager.executeWakefully( - { lifecycleManager.stopServices() }, - "LifecycleShutdown" - ) + LOG.info("Destroyed") + stopForeground(true) + if (receiver != null) unregisterReceiver(receiver) + wakeLockManager.executeWakefully({ + try { + if (started) { + lifecycleManager.stopServices() + lifecycleManager.waitForShutdown() + } + } catch (e: InterruptedException) { + LOG.info("Interrupted while waiting for shutdown") + } + LOG.info("Exiting") + exitProcess(0) + }, "LifecycleShutdown") }, "LifecycleShutdown") } } diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt index 9ca00b30f7441bae9d11dc2df903950ff3e1f0e7..3dd9c84dcab8dd1997df7a3fe0d59c09bc8e9d15 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt @@ -5,17 +5,31 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.SavedStateHandle import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.StateFlow +import org.briarproject.mailbox.core.lifecycle.LifecycleManager +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState import javax.inject.Inject @HiltViewModel class MailboxViewModel @Inject constructor( app: Application, handle: SavedStateHandle, + lifecycleManager: LifecycleManager, ) : AndroidViewModel(app) { private val _text = handle.getLiveData("text", "Hello Mailbox") val text: LiveData<String> = _text + val lifecycleState: StateFlow<LifecycleState> = lifecycleManager.lifecycleStateFlow + + fun startLifecycle() { + MailboxService.startService(getApplication()) + } + + fun stopLifecycle() { + MailboxService.stopService(getApplication()) + } + fun updateText(str: String) { _text.value = str } diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MainActivity.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MainActivity.kt index 6d338040768cf0d36560aeef7fd41ac63d427e86..679e69accb50dff7bd9c761633a790ffe3a4e461 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MainActivity.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MainActivity.kt @@ -5,13 +5,21 @@ import android.widget.Button import android.widget.TextView import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch import org.briarproject.mailbox.R +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState @AndroidEntryPoint class MainActivity : AppCompatActivity() { private val viewModel: MailboxViewModel by viewModels() + private lateinit var statusTextView: TextView + private lateinit var startStopButton: Button override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -19,13 +27,46 @@ class MainActivity : AppCompatActivity() { val textView = findViewById<TextView>(R.id.text) val button = findViewById<Button>(R.id.button) + statusTextView = findViewById(R.id.statusTextView) + startStopButton = findViewById(R.id.startStopButton) button.setOnClickListener { viewModel.updateText("Tested") } + // Start a coroutine in the lifecycle scope + lifecycleScope.launch { + // repeatOnLifecycle launches the block in a new coroutine every time the + // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED. + repeatOnLifecycle(Lifecycle.State.STARTED) { + // Trigger the flow and start listening for values. + // Note that this happens when lifecycle is STARTED and stops + // collecting when the lifecycle is STOPPED + viewModel.lifecycleState.collect { onLifecycleStateChanged(it) } + } + } + viewModel.text.observe(this, { text -> textView.text = text }) } + + private fun onLifecycleStateChanged(state: LifecycleState) = when (state) { + LifecycleState.STOPPED -> { + statusTextView.text = state.name + startStopButton.setText(R.string.start) + startStopButton.setOnClickListener { viewModel.startLifecycle() } + startStopButton.isEnabled = true + } + LifecycleState.RUNNING -> { + statusTextView.text = state.name + startStopButton.setText(R.string.stop) + startStopButton.setOnClickListener { viewModel.stopLifecycle() } + startStopButton.isEnabled = true + } + else -> { + statusTextView.text = state.name + startStopButton.isEnabled = false + } + } } diff --git a/mailbox-android/src/main/res/layout/activity_main.xml b/mailbox-android/src/main/res/layout/activity_main.xml index 62bd8a4a775e0859a8e5bab58d0384860b85a1fe..26cce69f629360bc5c346f67b7cce544aed60c4a 100644 --- a/mailbox-android/src/main/res/layout/activity_main.xml +++ b/mailbox-android/src/main/res/layout/activity_main.xml @@ -26,4 +26,27 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/text" /> -</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file + <TextView + android:id="@+id/statusTextView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:gravity="center" + app:layout_constraintBottom_toTopOf="@+id/startStopButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button" + app:layout_constraintVertical_bias="1.0" + tools:text="STOPPED" /> + + <Button + android:id="@+id/startStopButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:text="@string/start" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/mailbox-android/src/main/res/values/strings.xml b/mailbox-android/src/main/res/values/strings.xml index 141a92ef8d2445e795e88c458af8431458542414..580b932af496ebd4ab45620215b1420d5d30a8b8 100644 --- a/mailbox-android/src/main/res/values/strings.xml +++ b/mailbox-android/src/main/res/values/strings.xml @@ -3,4 +3,6 @@ <string name="notification_channel_name">Briar Mailbox Channel</string> <string name="notification_mailbox_title">Briar Mailbox running</string> <string name="notification_mailbox_content">Waiting for messages…</string> + <string name="start">Start mailbox</string> + <string name="stop">Stop mailbox</string> </resources> \ No newline at end of file diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java index 8bf4e72483e842b7b12b10cf7f433ae6c07bd9f4..63719354388f2fd8af55660a4e5ddf476b9219a8 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java @@ -5,6 +5,8 @@ import org.briarproject.mailbox.core.system.Wakeful; import java.util.concurrent.ExecutorService; +import kotlinx.coroutines.flow.StateFlow; + /** * Manages the lifecycle of the app: opening and closing the * {@link DatabaseComponent} starting and stopping {@link Service Services}, @@ -27,7 +29,7 @@ public interface LifecycleManager { */ enum LifecycleState { - STARTING, MIGRATING_DATABASE, COMPACTING_DATABASE, STARTING_SERVICES, + STOPPED, STARTING, MIGRATING_DATABASE, COMPACTING_DATABASE, STARTING_SERVICES, RUNNING, STOPPING; public boolean isAfter(LifecycleState state) { @@ -92,6 +94,8 @@ public interface LifecycleManager { */ LifecycleState getLifecycleState(); + StateFlow<LifecycleState> getLifecycleStateFlow(); + interface OpenDatabaseHook { /** * Called when the database is being opened, before diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.java deleted file mode 100644 index 6bd84052dfbc0304e90b6a3fb6d04699be9d50d3..0000000000000000000000000000000000000000 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.java +++ /dev/null @@ -1,168 +0,0 @@ -package org.briarproject.mailbox.core.lifecycle; - -import static org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE; -import static org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE; -import static org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.RUNNING; -import static org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.STARTING; -import static org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES; -import static org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.STOPPING; -import static org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING; -import static org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR; -import static org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SUCCESS; -import static org.briarproject.mailbox.core.util.LogUtils.info; -import static org.briarproject.mailbox.core.util.LogUtils.logDuration; -import static org.briarproject.mailbox.core.util.LogUtils.logException; -import static org.briarproject.mailbox.core.util.LogUtils.now; -import static org.briarproject.mailbox.core.util.LogUtils.trace; -import static org.slf4j.LoggerFactory.getLogger; - -import org.briarproject.mailbox.core.db.DatabaseComponent; -import org.briarproject.mailbox.core.db.MigrationListener; -import org.slf4j.Logger; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Semaphore; - -import javax.annotation.concurrent.ThreadSafe; -import javax.inject.Inject; - -@ThreadSafe -class LifecycleManagerImpl implements LifecycleManager, MigrationListener { - - private static final Logger LOG = getLogger(LifecycleManagerImpl.class); - - private final DatabaseComponent db; - private final List<Service> services; - private final List<OpenDatabaseHook> openDatabaseHooks; - private final List<ExecutorService> executors; - private final Semaphore startStopSemaphore = new Semaphore(1); - private final CountDownLatch dbLatch = new CountDownLatch(1); - private final CountDownLatch startupLatch = new CountDownLatch(1); - private final CountDownLatch shutdownLatch = new CountDownLatch(1); - - private volatile LifecycleState state = STARTING; - - @Inject - LifecycleManagerImpl(DatabaseComponent db) { - this.db = db; - services = new CopyOnWriteArrayList<>(); - openDatabaseHooks = new CopyOnWriteArrayList<>(); - executors = new CopyOnWriteArrayList<>(); - } - - @Override - public void registerService(Service s) { - info(LOG, () -> "Registering service " + s.getClass().getSimpleName()); - services.add(s); - } - - @Override - public void registerOpenDatabaseHook(OpenDatabaseHook hook) { - info(LOG, () -> "Registering open database hook " + hook.getClass().getSimpleName()); - openDatabaseHooks.add(hook); - } - - @Override - public void registerForShutdown(ExecutorService e) { - info(LOG, () -> "Registering executor " + e.getClass().getSimpleName()); - executors.add(e); - } - - @Override - public StartResult startServices() { - if (!startStopSemaphore.tryAcquire()) { - LOG.info("Already starting or stopping"); - return ALREADY_RUNNING; - } - try { - LOG.info("Opening database"); - long start = now(); - boolean reopened = db.open(this); - if (reopened) logDuration(LOG, () -> "Reopening database", start); - else logDuration(LOG, () -> "Creating database", start); - - LOG.info("Starting services"); - state = STARTING_SERVICES; - dbLatch.countDown(); - - for (Service s : services) { - start = now(); - s.startService(); - logDuration(LOG, () -> "Starting service " + s.getClass().getSimpleName(), start); - } - - state = RUNNING; - startupLatch.countDown(); - return SUCCESS; - } catch (ServiceException e) { - logException(LOG, e); - return SERVICE_ERROR; - } finally { - startStopSemaphore.release(); - } - } - - @Override - public void onDatabaseMigration() { - state = MIGRATING_DATABASE; - } - - @Override - public void onDatabaseCompaction() { - state = COMPACTING_DATABASE; - } - - @Override - public void stopServices() { - try { - startStopSemaphore.acquire(); - } catch (InterruptedException e) { - LOG.warn("Interrupted while waiting to stop services"); - return; - } - try { - LOG.info("Stopping services"); - state = STOPPING; - for (Service s : services) { - long start = now(); - s.stopService(); - logDuration(LOG, () -> "Stopping service " + s.getClass().getSimpleName(), start); - } - for (ExecutorService e : executors) { - trace(LOG, () -> "Stopping executor " + e.getClass().getSimpleName()); - e.shutdownNow(); - } - long start = now(); - db.close(); - logDuration(LOG, () -> "Closing database", start); - shutdownLatch.countDown(); - } catch (ServiceException e) { - logException(LOG, e); - } finally { - startStopSemaphore.release(); - } - } - - @Override - public void waitForDatabase() throws InterruptedException { - dbLatch.await(); - } - - @Override - public void waitForStartup() throws InterruptedException { - startupLatch.await(); - } - - @Override - public void waitForShutdown() throws InterruptedException { - shutdownLatch.await(); - } - - @Override - public LifecycleState getLifecycleState() { - return state; - } -} diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt new file mode 100644 index 0000000000000000000000000000000000000000..d5e659d0c5c455419179f82bb52ea561e8edd515 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt @@ -0,0 +1,163 @@ +package org.briarproject.mailbox.core.lifecycle + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import org.briarproject.mailbox.core.db.DatabaseComponent +import org.briarproject.mailbox.core.db.MigrationListener +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.RUNNING +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.STARTING +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.STOPPED +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.STOPPING +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.OpenDatabaseHook +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SUCCESS +import org.briarproject.mailbox.core.util.LogUtils.info +import org.briarproject.mailbox.core.util.LogUtils.logDuration +import org.briarproject.mailbox.core.util.LogUtils.logException +import org.briarproject.mailbox.core.util.LogUtils.now +import org.briarproject.mailbox.core.util.LogUtils.trace +import org.slf4j.LoggerFactory.getLogger +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.CountDownLatch +import java.util.concurrent.ExecutorService +import java.util.concurrent.Semaphore +import javax.annotation.concurrent.ThreadSafe +import javax.inject.Inject + +@ThreadSafe +internal class LifecycleManagerImpl @Inject constructor(private val db: DatabaseComponent) : + LifecycleManager, MigrationListener { + + companion object { + private val LOG = getLogger(LifecycleManagerImpl::class.java) + } + + private val services: MutableList<Service> + private val openDatabaseHooks: MutableList<OpenDatabaseHook> + private val executors: MutableList<ExecutorService> + private val startStopSemaphore = Semaphore(1) + private val dbLatch = CountDownLatch(1) + private val startupLatch = CountDownLatch(1) + private val shutdownLatch = CountDownLatch(1) + private val state = MutableStateFlow(STOPPED) + + init { + services = CopyOnWriteArrayList() + openDatabaseHooks = CopyOnWriteArrayList() + executors = CopyOnWriteArrayList() + } + + override fun registerService(s: Service) { + LOG.info { "Registering service ${s.javaClass.simpleName}" } + services.add(s) + } + + override fun registerOpenDatabaseHook(hook: OpenDatabaseHook) { + LOG.info { "Registering open database hook ${hook.javaClass.simpleName}" } + openDatabaseHooks.add(hook) + } + + override fun registerForShutdown(e: ExecutorService) { + LOG.info { "Registering executor ${e.javaClass.simpleName}" } + executors.add(e) + } + + override fun startServices(): StartResult { + if (!startStopSemaphore.tryAcquire()) { + LOG.info("Already starting or stopping") + return ALREADY_RUNNING + } + state.compareAndSet(STOPPED, STARTING) + return try { + LOG.info("Opening database") + var start = now() + val reopened = db.open(this) + if (reopened) logDuration(LOG, { "Reopening database" }, start) + else logDuration(LOG, { "Creating database" }, start) + LOG.info("Starting services") + state.value = STARTING_SERVICES + dbLatch.countDown() + for (s in services) { + start = now() + s.startService() + logDuration(LOG, { "Starting service ${s.javaClass.simpleName}" }, start) + } + state.compareAndSet(STARTING_SERVICES, RUNNING) + startupLatch.countDown() + SUCCESS + } catch (e: ServiceException) { + logException(LOG, e) + SERVICE_ERROR + } finally { + startStopSemaphore.release() + } + } + + override fun onDatabaseMigration() { + state.value = MIGRATING_DATABASE + } + + override fun onDatabaseCompaction() { + state.value = COMPACTING_DATABASE + } + + override fun stopServices() { + try { + startStopSemaphore.acquire() + } catch (e: InterruptedException) { + LOG.warn("Interrupted while waiting to stop services") + return + } + try { + LOG.info("Stopping services") + state.value = STOPPING + for (s in services) { + val start = now() + s.stopService() + logDuration(LOG, { "Stopping service " + s.javaClass.simpleName }, start) + } + for (e in executors) { + LOG.trace { "Stopping executor ${e.javaClass.simpleName}" } + e.shutdownNow() + } + val start = now() + db.close() + logDuration(LOG, { "Closing database" }, start) + shutdownLatch.countDown() + } catch (e: ServiceException) { + logException(LOG, e) + } finally { + startStopSemaphore.release() + state.compareAndSet(STOPPING, STOPPED) + } + } + + @Throws(InterruptedException::class) + override fun waitForDatabase() { + dbLatch.await() + } + + @Throws(InterruptedException::class) + override fun waitForStartup() { + startupLatch.await() + } + + @Throws(InterruptedException::class) + override fun waitForShutdown() { + shutdownLatch.await() + } + + override fun getLifecycleState(): LifecycleState { + return state.value + } + + override fun getLifecycleStateFlow(): StateFlow<LifecycleState> { + return state + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorPlugin.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorPlugin.java index 9f73da2e5f9c37927d75a873a763c889b4a34fdb..db5edc719efc6abb447868d29c4fcfbd181bf7aa 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorPlugin.java +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorPlugin.java @@ -26,6 +26,7 @@ import net.freehaven.tor.control.TorControlConnection; import org.briarproject.mailbox.core.PoliteExecutor; import org.briarproject.mailbox.core.lifecycle.Service; import org.briarproject.mailbox.core.lifecycle.ServiceException; +import org.briarproject.mailbox.core.server.WebServerManager; import org.briarproject.mailbox.core.system.Clock; import org.briarproject.mailbox.core.system.LocationUtils; import org.briarproject.mailbox.core.system.ResourceProvider; @@ -215,7 +216,7 @@ abstract class TorPlugin implements Service, EventHandler { // Check whether we're online updateConnectionStatus(networkManager.getNetworkStatus()); // Create a hidden service if necessary - ioExecutor.execute(() -> publishHiddenService("8888")); + ioExecutor.execute(() -> publishHiddenService(String.valueOf(WebServerManager.PORT))); } private boolean assetsAreUpToDate() { @@ -585,18 +586,6 @@ abstract class TorPlugin implements Service, EventHandler { // callback.pluginStateChanged(getState()); } - // Doesn't affect getState() - synchronized boolean setServerSocket(ServerSocket ss) { - if (stopped || serverSocket != null) return false; - serverSocket = ss; - return true; - } - - // Doesn't affect getState() - synchronized void clearServerSocket(ServerSocket ss) { - if (serverSocket == ss) serverSocket = null; - } - synchronized State getState() { if (!started || stopped || !settingsChecked) { return STARTING_STOPPING;