package org.moire.ultrasonic.activity import android.app.AlertDialog import android.os.Bundle import android.util.Log import android.view.MenuItem import android.widget.Button import androidx.appcompat.app.ActionBar import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import com.google.android.material.switchmaterial.SwitchMaterial import com.google.android.material.textfield.TextInputLayout import java.io.IOException import java.net.MalformedURLException import java.net.URL import org.koin.android.viewmodel.ext.android.viewModel import org.moire.ultrasonic.BuildConfig import org.moire.ultrasonic.R import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse import org.moire.ultrasonic.data.ServerSetting import org.moire.ultrasonic.service.SubsonicRESTException import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.ErrorDialog import org.moire.ultrasonic.util.ModalBackgroundTask import org.moire.ultrasonic.util.Util import retrofit2.Response /** * This Activity provides a Form which can be used to edit the properties of a Server Setting. * It can also be used to create a Server Setting from scratch. * Contains functions for testing the configured Server Setting */ internal class EditServerActivity : AppCompatActivity() { companion object { private val TAG = EditServerActivity::class.simpleName const val EDIT_SERVER_INTENT_INDEX = "index" } private val serverSettingsModel: ServerSettingsModel by viewModel() private var currentServerSetting: ServerSetting? = null private var serverNameEditText: TextInputLayout? = null private var serverAddressEditText: TextInputLayout? = null private var userNameEditText: TextInputLayout? = null private var passwordEditText: TextInputLayout? = null private var selfSignedSwitch: SwitchMaterial? = null private var ldapSwitch: SwitchMaterial? = null private var jukeboxSwitch: SwitchMaterial? = null private var saveButton: Button? = null private var testButton: Button? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) applyTheme() if (savedInstanceState == null) configureActionBar() setContentView(R.layout.server_edit) serverNameEditText = findViewById(R.id.edit_server_name) serverAddressEditText = findViewById(R.id.edit_server_address) userNameEditText = findViewById(R.id.edit_server_username) passwordEditText = findViewById(R.id.edit_server_password) selfSignedSwitch = findViewById(R.id.edit_self_signed) ldapSwitch = findViewById(R.id.edit_ldap) jukeboxSwitch = findViewById(R.id.edit_jukebox) saveButton = findViewById(R.id.edit_save) testButton = findViewById(R.id.edit_test) val index = intent.getIntExtra(EDIT_SERVER_INTENT_INDEX, -1) if (index != -1) { // Editing an existing server setTitle(R.string.server_editor_label) val serverSetting = serverSettingsModel.getServerSetting(index) serverSetting.observe( this, Observer { t -> if (t != null) { currentServerSetting = t setFields() } } ) saveButton!!.setOnClickListener { if (currentServerSetting != null) { if (getFields()) { serverSettingsModel.updateItem(currentServerSetting) finish() } } } } else { // Creating a new server setTitle(R.string.server_editor_new_label) saveButton!!.setOnClickListener { currentServerSetting = ServerSetting() if (getFields()) { serverSettingsModel.saveNewItem(currentServerSetting) finish() } } } testButton!!.setOnClickListener { if (getFields()) { testConnection() } } } override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == android.R.id.home) { if (areFieldsChanged()) { AlertDialog.Builder(this) .setIcon(android.R.drawable.ic_dialog_alert) .setTitle(R.string.common_confirm) .setMessage(R.string.server_editor_leave_confirmation) .setPositiveButton(R.string.common_ok) { dialog, _ -> dialog.dismiss() finish() } .setNegativeButton(R.string.common_cancel) { dialog, _ -> dialog.dismiss() } .show() } else { finish() } return true } return super.onOptionsItemSelected(item) } private fun applyTheme() { val theme = Util.getTheme(this) if ( "dark".equals(theme, ignoreCase = true) || "fullscreen".equals(theme, ignoreCase = true) ) { setTheme(R.style.UltraSonicTheme) } else if ( "light".equals(theme, ignoreCase = true) || "fullscreenlight".equals(theme, ignoreCase = true) ) { setTheme(R.style.UltraSonicTheme_Light) } } private fun configureActionBar() { val actionBar: ActionBar? = supportActionBar if (actionBar != null) { actionBar.setDisplayShowHomeEnabled(true) actionBar.setDisplayHomeAsUpEnabled(true) } } /** * Sets the values of the Form from the current Server Setting instance */ private fun setFields() { if (currentServerSetting == null) return serverNameEditText!!.editText?.setText(currentServerSetting!!.name) serverAddressEditText!!.editText?.setText(currentServerSetting!!.url) userNameEditText!!.editText?.setText(currentServerSetting!!.userName) passwordEditText!!.editText?.setText(currentServerSetting!!.password) selfSignedSwitch!!.isChecked = currentServerSetting!!.allowSelfSignedCertificate ldapSwitch!!.isChecked = currentServerSetting!!.ldapSupport jukeboxSwitch!!.isChecked = currentServerSetting!!.jukeboxByDefault } /** * Retrieves the values in the Form to the current Server Setting instance * This function also does some basic validation on the fields */ private fun getFields(): Boolean { if (currentServerSetting == null) return false var isValid = true var url: URL? = null if (serverAddressEditText!!.editText?.text.isNullOrBlank()) { serverAddressEditText!!.error = getString(R.string.server_editor_required) isValid = false } else { try { val urlString = serverAddressEditText!!.editText?.text.toString() url = URL(urlString) if (urlString != urlString.trim(' ') || urlString.contains("@")) { throw MalformedURLException() } serverAddressEditText!!.error = null } catch (exception: MalformedURLException) { serverAddressEditText!!.error = getString(R.string.settings_invalid_url) isValid = false } } if (serverNameEditText!!.editText?.text.isNullOrBlank()) { if (isValid && url != null) { serverNameEditText!!.editText?.setText(url.host) } } if (userNameEditText!!.editText?.text.isNullOrBlank()) { userNameEditText!!.error = getString(R.string.server_editor_required) isValid = false } else { userNameEditText!!.error = null } if (isValid) { currentServerSetting!!.name = serverNameEditText!!.editText?.text.toString() currentServerSetting!!.url = serverAddressEditText!!.editText?.text.toString() currentServerSetting!!.userName = userNameEditText!!.editText?.text.toString() currentServerSetting!!.password = passwordEditText!!.editText?.text.toString() currentServerSetting!!.allowSelfSignedCertificate = selfSignedSwitch!!.isChecked currentServerSetting!!.ldapSupport = ldapSwitch!!.isChecked currentServerSetting!!.jukeboxByDefault = jukeboxSwitch!!.isChecked } return isValid } /** * Checks whether any value in the fields are changed according to their original values. */ private fun areFieldsChanged(): Boolean { if (currentServerSetting == null) { return !serverNameEditText!!.editText?.text!!.isBlank() || !serverAddressEditText!!.editText?.text!!.isBlank() || !userNameEditText!!.editText?.text!!.isBlank() || !passwordEditText!!.editText?.text!!.isBlank() } return currentServerSetting!!.name != serverNameEditText!!.editText?.text.toString() || currentServerSetting!!.url != serverAddressEditText!!.editText?.text.toString() || currentServerSetting!!.userName != userNameEditText!!.editText?.text.toString() || currentServerSetting!!.password != passwordEditText!!.editText?.text.toString() || currentServerSetting!!.allowSelfSignedCertificate != selfSignedSwitch!!.isChecked || currentServerSetting!!.ldapSupport != ldapSwitch!!.isChecked || currentServerSetting!!.jukeboxByDefault != jukeboxSwitch!!.isChecked } /** * Tests if the network connection to the entered Server Settings can be made */ private fun testConnection() { val task: ModalBackgroundTask = object : ModalBackgroundTask( this, false ) { @Throws(Throwable::class) override fun doInBackground(): Boolean { updateProgress(R.string.settings_testing_connection) val configuration = SubsonicClientConfiguration( currentServerSetting!!.url, currentServerSetting!!.userName, currentServerSetting!!.password, SubsonicAPIVersions.getClosestKnownClientApiVersion( Constants.REST_PROTOCOL_VERSION ), Constants.REST_CLIENT_ID, currentServerSetting!!.allowSelfSignedCertificate, currentServerSetting!!.ldapSupport, BuildConfig.DEBUG ) val subsonicApiClient = SubsonicAPIClient(configuration) val pingResponse = subsonicApiClient.api.ping().execute() checkResponseSuccessful(pingResponse) val licenseResponse = subsonicApiClient.api.getLicense().execute() checkResponseSuccessful(licenseResponse) return licenseResponse.body()!!.license.valid } override fun done(licenseValid: Boolean) { if (licenseValid) { Util.toast(activity, R.string.settings_testing_ok) } else { Util.toast(activity, R.string.settings_testing_unlicensed) } } override fun error(error: Throwable) { Log.w(TAG, error.toString(), error) ErrorDialog( activity, String.format( "%s %s", resources.getString(R.string.settings_connection_failure), getErrorMessage(error) ), false ) } } task.execute() } /** * Checks the Subsonic Response for application specific errors */ private fun checkResponseSuccessful(response: Response) { if ( response.isSuccessful && response.body()!!.status === SubsonicResponse.Status.OK ) { return } if (!response.isSuccessful) { throw IOException("Server error, code: " + response.code()) } else if ( response.body()!!.status === SubsonicResponse.Status.ERROR && response.body()!!.error != null ) { throw SubsonicRESTException(response.body()!!.error!!) } else { throw IOException("Failed to perform request: " + response.code()) } } }