borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/Configuration.kt
@@ -30,6 +30,8 @@ /** * The borg version to install from github (optional). */ // @JsonIgnore needed by client: ConfigurationserverTab.jsx fails otherwise (conflicting borgVersion fields). @JsonIgnore var borgVersion: String? = null /** borgbutler-docker/buildDocker.sh
@@ -11,7 +11,13 @@ echo "Building docker file..." (cd app; docker build -t kreinhard/borgbutler .) echo "Push: docker push kreinhard/borgbutler:tagname" echo "Run without ssh: 'docker run -v $HOME/BorgButler:/BorgButler -p 127.0.0.1:9042:9042 --name borgbutler kreinhard/borgbutler'" echo "docker tag ..... version" echo "docker push kreinhard/borgbutler:version" echo "docker push kreinhard/borgbutler:latest" echo echo echo "Run without ssh: 'docker run -v $HOME/BorgButler:/BorgButler -p 127.0.0.1:9042:9042 --name borgbutler kreinhard/borgbutler'" echo "Run with ssh: 'docker run -v $HOME/BorgButler:/BorgButler -v $HOME/.ssh:/home/borgbutler/.ssh:ro -p 127.0.0.1:9042:9042 --name borgbutler kreinhard/borgbutler'" echo echo 'Increase Java memory: docker run -e JAVA_OPTS="-Xms4g -Xmx4g" -v ...' borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/BorgButlerApplication.kt
@@ -112,7 +112,7 @@ } val uri = URI.create(url) val quietMode = line.hasOption('q') if (!quietMode && RunningMode.desktopSupportsBrowse && RunningMode.headlessMode) { if (!quietMode && RunningMode.desktopSupportsBrowse) { try { log.info { "Trying to open your local web browser: $uri" } Desktop.getDesktop().browse(uri) @@ -123,10 +123,10 @@ } else { if (quietMode) { log.info("Server started in quiet mode (option -q). Please open your browser manually: $uri") } else if (!RunningMode.desktopSupportsBrowse) { log.info("Desktop not available. Please open your browser manually: $uri") } else if (RunningMode.headlessMode) { log.info("Desktop not available in headless mode. Please open your browser manually: $uri") } else if (!RunningMode.desktopSupportsBrowse) { log.info("Desktop not available. Please open your browser manually: $uri") } } } catch (ex: ParseException) { borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/RunningMode.kt
@@ -10,7 +10,7 @@ var webDevelopment: Boolean = false internal set val headlessMode: Boolean = System.getProperty("java.awt.headless") == "true" val desktopSupported = Desktop.isDesktopSupported() val desktopSupported = !headlessMode && Desktop.isDesktopSupported() val desktopSupportsBrowse = desktopSupported && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE) @JvmStatic borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/ServerConfiguration.kt
@@ -3,10 +3,6 @@ import de.micromata.borgbutler.config.Configuration import de.micromata.borgbutler.config.ConfigurationHandler.Companion.getConfiguration import mu.KotlinLogging import org.apache.commons.lang3.StringUtils import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component private val log = KotlinLogging.logger {} borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ArchivesRest.kt
@@ -34,7 +34,6 @@ /** * @param repoName Name of repository ([Repository.getName]. * @param archiveId Id or name of archive. * @param prettyPrinter If true then the json output will be in pretty format. * @return Repository (including list of archives) as json string. * @see JsonUtils.toJson */ @@ -42,14 +41,12 @@ fun getArchive( @RequestParam("repo") repoName: String, @RequestParam("archiveId") archiveId: String, @RequestParam("force", required = false) force: Boolean, @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean ): String { val archive: Archive = ButlerCache.getInstance().getArchive(repoName, archiveId, force == true) @RequestParam("force", required = false) force: Boolean? ): Archive? { val archive = ButlerCache.getInstance().getArchive(repoName, archiveId, force == true) if (force == true) { ButlerCache.getInstance().deleteCachedArchiveContent(repoName, archiveId) } return JsonUtils.toJson(archive, prettyPrinter == true) return archive } /** @@ -61,8 +58,7 @@ * @param diffArchiveId If given, the differences between archiveId and diffArchiveId will be returned. * @param autoChangeDirectoryToLeafItem If given, this method will step automatically into single sub directories. * @param force If false (default), non cached file lists will not be loaded by borg. * @param prettyPrinter If true then the json output will be in pretty format. * @return Repository (including list of archives) as json string. * @return Repository (including list of archives) as json string or [{"mode": "notLoaded"}] if no file list loaded. * @see JsonUtils.toJson */ @GetMapping("filelist") @@ -74,8 +70,7 @@ @RequestParam("maxResultSize", required = false) maxResultSize: String?, @RequestParam("diffArchiveId", required = false) diffArchiveId: String?, @RequestParam("autoChangeDirectoryToLeafItem", required = false) autoChangeDirectoryToLeafItem: Boolean?, @RequestParam("force", required = false) force: Boolean?, @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean? @RequestParam("force", required = false) force: Boolean? ): String { val diffMode = StringUtils.isNotBlank(diffArchiveId) val maxSize = NumberUtils.toInt(maxResultSize, 50) @@ -107,7 +102,7 @@ return "[{\"mode\": \"notLoaded\"}]" } } return JsonUtils.toJson(items, prettyPrinter == true) return JsonUtils.toJson(items) } /** borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/BorgRepoConfigsRest.kt
@@ -18,17 +18,14 @@ class BorgRepoConfigsRest { /** * @param id id or name of repo. * @param prettyPrinter If true then the json output will be in pretty format. * @return [BorgRepoConfig] as json string. * @see JsonUtils.toJson */ @GetMapping fun getRepoConfig( @RequestParam("id") id: String, @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean? ): String { val repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(id) return JsonUtils.toJson(repoConfig, prettyPrinter == true) @RequestParam("id") id: String ): BorgRepoConfig? { return ConfigurationHandler.getConfiguration().getRepoConfig(id) } @PostMapping borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ConfigurationRest.kt
@@ -17,16 +17,12 @@ @RequestMapping("/rest/configuration") class ConfigurationRest { /** * @param prettyPrinter If true then the json output will be in pretty format. * @see JsonUtils.toJson */ @GetMapping("config") fun getConfig(@RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?): String { fun getConfig(): ConfigurationInfo { val configurationInfo = ConfigurationInfo() configurationInfo.serverConfiguration = ServerConfiguration.get() configurationInfo.borgVersion = BorgInstallation.getInstance().getBorgVersion() return JsonUtils.toJson(configurationInfo, prettyPrinter) configurationInfo.borgVersion = BorgInstallation.getInstance().borgVersion return configurationInfo } @PostMapping("config") @@ -41,25 +37,20 @@ configurationHandler.save() } /** * @param prettyPrinter If true then the json output will be in pretty format. * @see JsonUtils.toJson */ @GetMapping("user") fun getUser(@RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?): String { val user: UserData = RestUtils.getUser() return JsonUtils.toJson(user, prettyPrinter) fun getUser(): UserData { return RestUtils.getUser() } @PostMapping("user") fun setUser(@RequestBody user: UserData) { if (user.getLocale() != null && StringUtils.isBlank(user.getLocale().getLanguage())) { if (user.locale?.language?.isBlank() == true) { // Don't set locale with "" as language. user.setLocale(null) user.locale = null } if (StringUtils.isBlank(user.getDateFormat())) { if (user.dateFormat?.isBlank() == true) { // Don't set dateFormat as "". user.setDateFormat(null) user.dateFormat = null } UserManager.instance().saveUser(user) } borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/I18nRest.kt
@@ -19,24 +19,20 @@ * @param request For detecting the user's client locale. * @param locale If not given, the client's language (browser) will be used. * @param keysOnly If true, only the keys will be returned. Default is false. * @param prettyPrinter If true then the json output will be in pretty format. * @see JsonUtils.toJson */ @GetMapping("list") fun getList( request: HttpServletRequest, @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?, @RequestParam("keysOnly", required = false) keysOnly: Boolean?, @RequestParam("locale", required = false) locale: String? ): String { ): Map<String, String> { val localeObject: Locale? if (StringUtils.isNotBlank(locale)) { localeObject = Locale(locale) } else { localeObject = RestUtils.getUserLocale(request) } val translations: Map<String, String> = I18nClientMessages.getInstance().getAllMessages(localeObject, keysOnly == true) return JsonUtils.toJson(translations, prettyPrinter) return I18nClientMessages.getInstance().getAllMessages(localeObject, keysOnly == true) } } borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/JacksonConfig.kt
New file @@ -0,0 +1,36 @@ package de.micromata.borgbutler.server.rest import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.KotlinModule import de.micromata.borgbutler.server.RunningMode import mu.KotlinLogging import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Primary private val log = KotlinLogging.logger {} @Configuration open class JacksonConfig { private var objectMapper: ObjectMapper? = null @Bean @Primary open fun objectMapper(): ObjectMapper { objectMapper?.let { return it } val mapper = ObjectMapper() mapper.registerModule(KotlinModule()) mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true) mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false) val failOnUnknownJsonProperties = RunningMode.runningInIDE if (failOnUnknownJsonProperties) { log.warn("Unknown JSON properties are not allowed in REST call, due to configuration in projectforge.properties:projectforge.rest.json.failOnUnknownJsonProperties (OK, but Rest calls may fail).") } mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownJsonProperties) // Should be true in development mode! objectMapper = mapper return mapper } } borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/JobsRest.kt
@@ -23,7 +23,6 @@ /** * @param repo If given, only the job queue of the given repo will be returned. * @param testMode If true, then a test job list is created. * @param prettyPrinter If true then the json output will be in pretty format. * @return Job queues as json string. * @see JsonUtils.toJson */ @@ -31,13 +30,12 @@ fun getJobs( @RequestParam("repo", required = false) repo: String?, @RequestParam("testMode", required = false) testMode: Boolean?, @RequestParam("oldJobs", required = false) oldJobs: Boolean?, @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean? ): String { @RequestParam("oldJobs", required = false) oldJobs: Boolean? ): List<JsonJobQueue> { log.debug("getJobs repo=$repo, oldJobs=$oldJobs") if (testMode == true) { // Return dynamic test queue: return returnTestList(oldJobs, prettyPrinter) return returnTestList(oldJobs) } var validRepo = false if (StringUtils.isNotBlank(repo) && "null" != repo && "undefined" != repo) { @@ -46,19 +44,17 @@ val borgQueueExecutor = BorgQueueExecutor.getInstance() val queueList = mutableListOf<JsonJobQueue>() if (validRepo) { // Get only the queue of the given repo: val queue = getQueue(repo, oldJobs) if (queue != null) { queueList.add(queue) getQueue(repo, oldJobs)?.let { queueList.add(it) } } else { // Get all the queues (of all repos). for (rep in borgQueueExecutor.getRepos()) { val queue = getQueue(rep, oldJobs) if (queue != null) { queueList.add(queue) for (rep in borgQueueExecutor.repos) { getQueue(rep, oldJobs)?.let { queueList.add(it) } } } return JsonUtils.toJson(queueList, prettyPrinter) return queueList } private fun getQueue(repo: String?, oldJobs: Boolean?): JsonJobQueue? { @@ -66,12 +62,8 @@ val repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(repo) ?: return null val borgJobList = borgQueueExecutor.getJobListCopy(repoConfig, oldJobs == true) if (CollectionUtils.isEmpty(borgJobList)) return null val queue: JsonJobQueue = JsonJobQueue().setRepo(repoConfig.getDisplayName()) queue.setJobs(mutableListOf()) for (borgJob in borgJobList) { val job = JsonJob(borgJob) queue.getJobs().add(job) } val queue: JsonJobQueue = JsonJobQueue().setRepo(repoConfig.displayName) queue.jobs = borgJobList.map { JsonJob(it) } return queue } @@ -94,10 +86,9 @@ * Only for test purposes and development. * * @param oldJobs * @param prettyPrinter * @return */ private fun returnTestList(oldJobs: Boolean?, prettyPrinter: Boolean?): String { private fun returnTestList(oldJobs: Boolean?): List<JsonJobQueue> { var list = if (oldJobs == true) oldJobsTestList else testList if (list == null) { list = mutableListOf() @@ -117,11 +108,11 @@ } } else if (oldJobs != true) { for (jobQueue in list) { for (job in jobQueue.getJobs()) { if (job.getStatus() != AbstractJob.Status.RUNNING) continue var current: Long = job.getProgressInfo().getCurrent() val total: Long = job.getProgressInfo().getTotal() current += if (StringUtils.startsWith(job.getProgressInfo().getMessage(), "Calculating")) { for (job in jobQueue.jobs) { if (job.status != AbstractJob.Status.RUNNING) continue var current: Long = job.progressInfo.current val total: Long = job.progressInfo.total current += if (StringUtils.startsWith(job.progressInfo.message, "Calculating")) { // Info is a faster operation: (Math.random() * total / 5).toLong() } else { @@ -131,16 +122,16 @@ if (current > total) { current = 0 // Reset to beginning. } job.getProgressInfo().setCurrent(current) if (job.getProgressText().startsWith("Calculating")) { job.getProgressInfo() .setMessage("Calculating statistics... " + Math.round((100 * current / total).toFloat()) + "%") job.progressInfo.current = current if (job.progressText.startsWith("Calculating")) { job.progressInfo.message = "Calculating statistics... " + Math.round((100 * current / total).toFloat()) + "%" } job.buildProgressText() } } } return JsonUtils.toJson(list, prettyPrinter) return list } /** @@ -169,28 +160,28 @@ .setProgressInfo(progressInfo) .setStatus(AbstractJob.Status.QUEUED) if ("info" == operation) { progressInfo.setMessage("Calculating statistics... ") job.setDescription("Loading info of archive '" + host + "-2018-12-05T23:10:33' of repo '" + queue.getRepo() + "'.") .setCommandLineAsString("borg info --json --log-json --progress ssh://...:23/./backups/$host::$host-2018-12-05T23:10:33") progressInfo.message = "Calculating statistics... " job.setDescription("Loading info of archive '" + host + "-2018-12-05T23:10:33' of repo '" + queue.repo + "'.").commandLineAsString = "borg info --json --log-json --progress ssh://...:23/./backups/$host::$host-2018-12-05T23:10:33" } else { progressInfo.setMessage("Getting file list... ") job.setDescription("Loading list of files of archive '" + host + "-2018-12-05T17:30:38' of repo '" + queue.getRepo() + "'.") .setCommandLineAsString("borg list --json-lines ssh://...:23/./backups/$host::$host-2018-12-05T17:30:38") progressInfo.message = "Getting file list... " job.setDescription("Loading list of files of archive '" + host + "-2018-12-05T17:30:38' of repo '" + queue.repo + "'.").commandLineAsString = "borg list --json-lines ssh://...:23/./backups/$host::$host-2018-12-05T17:30:38" } job.buildProgressText() if (current >= 0) { job.setStatus(AbstractJob.Status.RUNNING) job.status = AbstractJob.Status.RUNNING } else { job.setStatus(AbstractJob.Status.QUEUED) job.status = AbstractJob.Status.QUEUED } if (queue.getJobs() == null) { queue.setJobs(ArrayList<JsonJob>()) if (queue.jobs == null) { queue.jobs = ArrayList<JsonJob>() } job.setUniqueJobNumber(uniqueNumber) job.uniqueJobNumber = uniqueNumber if (oldJobs) { job.setStatus(if (uniqueNumber % 2 == 0L) AbstractJob.Status.CANCELLED else AbstractJob.Status.DONE) job.status = if (uniqueNumber % 2 == 0L) AbstractJob.Status.CANCELLED else AbstractJob.Status.DONE } queue.getJobs().add(job) queue.jobs.add(job) return job } borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/LoggingRest.kt
@@ -1,11 +1,10 @@ package de.micromata.borgbutler.server.rest import de.micromata.borgbutler.json.JsonUtils import de.micromata.borgbutler.server.logging.LoggerMemoryAppender import de.micromata.borgbutler.server.logging.LogFilter import de.micromata.borgbutler.server.logging.LogLevel import de.micromata.borgbutler.server.logging.LoggerMemoryAppender import de.micromata.borgbutler.server.logging.LoggingEventData import mu.KotlinLogging import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam @@ -24,7 +23,6 @@ * @param maxSize Max size of the result list. * @param ascendingOrder Default is false (default is descending order). * @param lastReceivedOrderNumber The last received order number for updating log entries (preventing querying all entries again). * @param prettyPrinter * @return */ @GetMapping("query") @@ -34,34 +32,33 @@ @RequestParam("treshold", required = false) logLevelTreshold: String?, @RequestParam("maxSize", required = false) maxSize: Int?, @RequestParam("ascendingOrder", required = false) ascendingOrder: Boolean?, @RequestParam("lastReceivedOrderNumber", required = false) lastReceivedOrderNumber: Int?, @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean? ): String { @RequestParam("lastReceivedOrderNumber", required = false) lastReceivedOrderNumber: Int? ): List<LoggingEventData> { val filter = LogFilter() filter.setSearch(search) filter.search = search if (logLevelTreshold != null) { try { val treshold = LogLevel.valueOf(logLevelTreshold.trim { it <= ' ' } .toUpperCase()) filter.setThreshold(treshold) filter.threshold = treshold } catch (ex: IllegalArgumentException) { log.error("Can't parse log level treshold: " + logLevelTreshold + ". Supported values (case insensitive): " + LogLevel.getSupportedValues()) } } if (filter.getThreshold() == null) { filter.setThreshold(LogLevel.INFO) if (filter.threshold == null) { filter.threshold = LogLevel.INFO } if (maxSize != null) { filter.setMaxSize(maxSize) filter.maxSize = maxSize } if (ascendingOrder != null && ascendingOrder == true) { filter.setAscendingOrder(true) filter.isAscendingOrder = true } if (lastReceivedOrderNumber != null) { filter.setLastReceivedLogOrderNumber(lastReceivedOrderNumber) filter.lastReceivedLogOrderNumber = lastReceivedOrderNumber } val loggerMemoryAppender = LoggerMemoryAppender.getInstance() return JsonUtils.toJson(loggerMemoryAppender.query(filter, RestUtils.getUserLocale(request!!)), prettyPrinter) return loggerMemoryAppender.query(filter, RestUtils.getUserLocale(request!!)) } } borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ReposRest.kt
@@ -4,7 +4,6 @@ import de.micromata.borgbutler.data.Repository import de.micromata.borgbutler.json.JsonUtils import mu.KotlinLogging import org.apache.commons.collections4.CollectionUtils import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam @@ -16,51 +15,40 @@ @RequestMapping("/rest/repos") class ReposRest { /** * * @param prettyPrinter If true then the json output will be in pretty format. * @return A list of repositories of type [BorgRepository]. * @see JsonUtils.toJson */ @GetMapping("list") fun getList(@RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?): String { val repositories: List<Repository?> = ButlerCache.getInstance().getAllRepositories() return if (CollectionUtils.isEmpty(repositories)) { "[]" } else JsonUtils.toJson(repositories, prettyPrinter) fun getList(): List<Repository> { return ButlerCache.getInstance().allRepositories } /** * * @param id id or name of repo. * @param prettyPrinter If true then the json output will be in pretty format. * @return [Repository] (without list of archives) as json string. * @see JsonUtils.toJson */ @GetMapping("repo") fun getRepo(@RequestParam("id") id: String, @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?): String { val repository: Repository = ButlerCache.getInstance().getRepository(id) return JsonUtils.toJson(repository, prettyPrinter) fun getRepo(@RequestParam("id") id: String): Repository? { return ButlerCache.getInstance().getRepository(id) } /** * * @param id id or name of repo. * @param prettyPrinter If true then the json output will be in pretty format. * @return [Repository] (including list of archives) as json string. * @see JsonUtils.toJson */ @GetMapping("repoArchiveList") fun getRepoArchiveList( @RequestParam("id") id: String, @RequestParam("force", required = false) force: Boolean?, @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean? ): String { @RequestParam("force", required = false) force: Boolean? ): Repository? { if (force == true) { val repo: Repository = ButlerCache.getInstance().getRepository(id) ButlerCache.getInstance().clearRepoCacheAccess(repo) } val repository: Repository = ButlerCache.getInstance().getRepositoryArchives(id) return JsonUtils.toJson(repository, prettyPrinter) return ButlerCache.getInstance().getRepositoryArchives(id) } } borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/SystemInfoRest.kt
@@ -16,9 +16,9 @@ */ @GetMapping("info") fun statistics(): SystemInfo { val borgVersion = BorgInstallation.getInstance().getBorgVersion() val borgVersion = BorgInstallation.getInstance().borgVersion val systemInfonfo = SystemInfo() .setQueueStatistics(BorgQueueExecutor.getInstance().getStatistics()) .setQueueStatistics(BorgQueueExecutor.getInstance().statistics) .setConfigurationOK(borgVersion.isVersionOK) .setBorgVersion(borgVersion) return systemInfonfo borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/VersionRest.kt
@@ -6,7 +6,6 @@ import org.apache.commons.lang3.StringUtils import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import java.util.* import javax.servlet.http.HttpServletRequest @@ -17,28 +16,21 @@ /** * * @param request For detecting the user's client locale. * @param prettyPrinter If true then the json output will be in pretty format. * @see JsonUtils.toJson */ @GetMapping("version") fun getVersion( request: HttpServletRequest, @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean? ): String { fun getVersion(request: HttpServletRequest): MyVersion { val user = RestUtils.getUser() var language = Languages.asString(user.getLocale()) if (StringUtils.isBlank(language)) { val locale: Locale = request.locale language = locale.getLanguage() language = locale.language } val version = MyVersion(language, RestUtils.checkLocalDesktopAvailable(request) == null) return JsonUtils.toJson(version, prettyPrinter) return MyVersion(language, RestUtils.checkLocalDesktopAvailable(request) == null) } inner class MyVersion(language: String, localDesktopAvailable: Boolean) { private val version: Version val language: String val isLocalDesktopAvailable: Boolean inner class MyVersion(val language: String, val localDesktopAvailable: Boolean) { private val version: Version = Version.getInstance() val appName: String get() = version.appName @@ -57,10 +49,5 @@ val updateVersion: String? get() = version.updateVersion init { version = Version.getInstance() this.language = language isLocalDesktopAvailable = localDesktopAvailable } } }