Q35OTML226J2HZLHOCPV5OY6ZUM2XU4RZBE5E3GDWVHVFASHFEJAC
B43WNBLFFR2UQIH3C6KIZAQTAEQOQM3J3IYLGQMVGJHYOME73OKQC
FNNW5IEAXQ43WKB6QSQB7DFLG3Y3T5FYPXIUX7KQ2URR2GU3QLTAC
5AUENX2YJVFNKZUSPEPDNLLL7TKZS2WTFC6CABWSZK2EC4MNCRQAC
QXUEMZ3B2FUHFUC7ZZHJMH5FVWLEMEYXUMFA6JNXTJKIVZNMRIOAC
7L5LODGZ7AN4ZULDJZMLALD7PL6E57VZSNNSG67SFJARUJGCT47QC
ZCRW57C5MSBXYGUMGQTZNHGHO4HGHFBICW53X5I2IMGP3H2CKWRQC
FRFFQV7VNYKGCA7ZAOSRPC2HHYTAIZ6AGGR7A5QEV6QPAQGFDYGAC
6CR2EFUN7JXFHCBTNX3WWOOP4WFOCFO6KSPEBN6V6J5HFZO2LHNQC
OPFG6CZ26PPTGTH7ULLRQGZGR3YEIEJOV5W2E3WN7PFRZS62CVLQC
import com.github.jonathanxd.dracon.cache.HashKey
import com.github.jonathanxd.dracon.cache.HashMapCache
import com.github.jonathanxd.dracon.cache.PijulCache
import com.github.jonathanxd.dracon.cache.PijulLogEntryCache
import com.github.jonathanxd.dracon.cache.persist.DraconPersistentStateComponent
val change = this.doExecutionWithMapper("change-$hash", this.createPainlessExecPijulOperation(project, root, listOf("change", hash))) {
it.parseChange(hash)
val change = this.pijulLogEntryCache.queryOrCompute(hash) {
this.doExecutionWithMapper("change-$hash",
this.createPainlessExecPijulOperation(project, root, listOf("change", hash))
) {
it.parseChange(hash)
}
val change = this.doExecutionWithMapper("change-$hash", this.createPainlessExecPijulOperation(project, root, listOf("change", hash))) {
it.parseChange(hash)
val change = this.pijulLogEntryCache.queryOrCompute(hash) {
this.doExecutionWithMapper("change-$hash",
this.createPainlessExecPijulOperation(project, root, listOf("change", hash))
) {
it.parseChange(hash)
}
return PijulExecution(
input.linesToFlow().onEach {
draconConsoleWriter(project).logCommand("pijul", args, it)
},
error.linesToFlow().onEach {
draconConsoleWriter(project).logCommandError("pijul", args, it)
},
flow {
while (process.isAlive)
delay(delay)
val exit = process.exitValue()
input.split("\n").forEach {
draconConsoleWriter(project).logCommand("pijul", args, it)
}
error.split("\n").forEach {
draconConsoleWriter(project).logCommandError("pijul", args, it)
}
if (exit == 0) {
draconConsoleWriter(project).logCommand("pijul", args, "<Exit status> $exit")
} else {
draconConsoleWriter(project).logCommandError("pijul", args, "<Exit status> $exit")
}
val exit = process.waitFor()
if (exit == 0) {
draconConsoleWriter(project).logCommand("pijul", args, "<Exit status> $exit")
} else {
draconConsoleWriter(project).logCommandError("pijul", args, "<Exit status> $exit")
}
val input = process.inputStream
val error = process.errorStream
val input = String(process.inputStream.readAllBytes(), Charsets.UTF_8)
val error = String(process.errorStream.readAllBytes(), Charsets.UTF_8)
input.split("\n").forEach {
draconConsoleWriter(project).logCommand("pijul", args, it)
}
error.split("\n").forEach {
draconConsoleWriter(project).logCommandError("pijul", args, it)
}
val exit = process.waitFor()
if (exit == 0) {
draconConsoleWriter(project).logCommand("pijul", args, "<Exit status> $exit")
} else {
draconConsoleWriter(project).logCommandError("pijul", args, "<Exit status> $exit")
}
input.linesToFlow().onEach {
input,
error,
exit
)
}
/**
* Creates a [PijulExecution] operation that could be executed at any time. This operation uses Kotlin Coroutines
* and can be executed immediately through [doExecution] or through [doExecutionWithMapper].
*
* This implementation does not requires a delay value to be provided, like [createExecPijulOperation] does, instead
* it uses the kotlin conversion from `CompletionStage` to `Coroutines` and awaits the process through [Process.onExit].
*
* [doExecution] and [doExecutionWithMapper] does execution by scheduling task to [Dispatchers.IO], instead of Main Thread,
* offloading the Process execution handling to a different scheduler. However, mapping operation of [doExecutionWithMapper]
* is not offloaded from the caller context.
*
*/
@RequiresBackgroundThread
private fun <K: Any> createPainlessExecPijulOperationWithCache(project: Project,
dir: Path,
args: List<String>,
key: K): PijulExecution {
return this.cache.cache(key) {
val process = ProcessBuilder()
.command(listOf(this.findPijul()) + args)
.directory(dir.toFile())
.start()
val input = String(process.inputStream.readAllBytes(), Charsets.UTF_8)
val error = String(process.errorStream.readAllBytes(), Charsets.UTF_8)
input.split("\n").forEach {
val exit = process.exitValue()
if (exit == 0) {
draconConsoleWriter(project).logCommand("pijul", args, "<Exit status> $exit")
} else {
draconConsoleWriter(project).logCommandError("pijul", args, "<Exit status> $exit")
}
val exit = process.waitFor()
if (exit == 0) {
draconConsoleWriter(project).logCommand("pijul", args, "<Exit status> $exit")
} else {
draconConsoleWriter(project).logCommandError("pijul", args, "<Exit status> $exit")
}
return PijulExecution(
input.linesToFlow().onEach {
draconConsoleWriter(project).logCommand("pijul", args, it)
},
error.linesToFlow().onEach {
draconConsoleWriter(project).logCommandError("pijul", args, it)
},
flow {
process.onExit().await()
input.split("\n").forEach {
draconConsoleWriter(project).logCommand("pijul", args, it)
}
if (exit == 0) {
draconConsoleWriter(project).logCommand("pijul", args, "<Exit status> $exit")
} else {
draconConsoleWriter(project).logCommandError("pijul", args, "<Exit status> $exit")
}
val exit = process.waitFor()
if (exit == 0) {
draconConsoleWriter(project).logCommand("pijul", args, "<Exit status> $exit")
} else {
draconConsoleWriter(project).logCommandError("pijul", args, "<Exit status> $exit")
}
return PijulExecution(
input.linesToFlow().onEach {
draconConsoleWriter(project).logCommand("pijul", args, it)
},
error.linesToFlow().onEach {
draconConsoleWriter(project).logCommandError("pijul", args, it)
},
flow {
process.onExit().await()
input.split("\n").forEach {
draconConsoleWriter(project).logCommand("pijul", args, it)
}
if (exit == 0) {
draconConsoleWriter(project).logCommand("pijul", args, "<Exit status> $exit")
} else {
draconConsoleWriter(project).logCommandError("pijul", args, "<Exit status> $exit")
}
val exit = process.waitFor()
if (exit == 0) {
draconConsoleWriter(project).logCommand("pijul", args, "<Exit status> $exit")
} else {
draconConsoleWriter(project).logCommandError("pijul", args, "<Exit status> $exit")
}
package com.github.jonathanxd.dracon.cache.persist
import com.github.jonathanxd.dracon.log.PijulLog
import com.github.jonathanxd.dracon.log.PijulLogEntry
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import com.intellij.util.xmlb.annotations.Property
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.util.*
package com.github.jonathanxd.dracon.cache.persist
import com.github.jonathanxd.dracon.log.PijulLogEntry
import com.intellij.openapi.components.*
import com.intellij.util.xmlb.XmlSerializerUtil
import java.io.*
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.util.*
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
@State(name = "DraconLogEntries", storages = [Storage(value = StoragePathMacros.CACHE_FILE)], reloadable = true)
@Service
class DraconPersistentStateComponent() : PersistentStateComponent<DraconPersistentStateComponent> {
public var data: String? = null
override fun getState() = this
override fun loadState(state: DraconPersistentStateComponent) {
XmlSerializerUtil.copyBean(state, this);
}
}
fun String.toPijulLogEntries(): List<PijulLogEntry> {
val decoded = Base64.getDecoder().decode(this)
val reader = ObjectInputStream(ByteArrayInputStream(decoded))
return reader.readObject() as List<PijulLogEntry>
}
fun createLogEntriesPersist(entries: List<PijulLogEntry>): String {
val stream = ByteArrayOutputStream()
val writer = ObjectOutputStream(stream)
writer.writeObject(entries)
writer.close()
return Base64.getEncoder().encodeToString(stream.toByteArray())
}
fun Path.toPijulLogEntriesMap(): Map<String, PijulLogEntry> {
Files.newInputStream(this).use { stream ->
GZIPInputStream(stream).use { gz ->
ObjectInputStream(gz).use { reader ->
return reader.readObject() as Map<String, PijulLogEntry>
}
}
}
}
fun String.toPijulLogEntriesMap(): Map<String, PijulLogEntry> {
val decoded = Base64.getDecoder().decode(this)
val reader = ObjectInputStream(GZIPInputStream(ByteArrayInputStream(decoded)))
return reader.readObject() as Map<String, PijulLogEntry>
}
fun Path.createLogEntriesPersist(entries: Map<String, PijulLogEntry>) {
Files.newOutputStream(this, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING).use { writer ->
GZIPOutputStream(writer).use { compress ->
ObjectOutputStream(compress).use {oos ->
oos.writeObject(entries)
}
}
}
}
package com.github.jonathanxd.dracon.cache
import com.github.jonathanxd.dracon.cache.persist.createLogEntriesPersist
import com.github.jonathanxd.dracon.cache.persist.toPijulLogEntriesMap
import com.github.jonathanxd.dracon.log.PijulLogEntry
import com.github.jonathanxd.dracon.pijul.PijulOperationResult
import com.github.jonathanxd.dracon.pijul.SuccessStatusCode
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.getProjectDataPath
import java.nio.file.Files
import java.util.concurrent.ConcurrentHashMap
@Suppress("UnstableApiUsage")
class PijulLogEntryCache(val project: Project) {
private val map = ConcurrentHashMap<String, PijulLogEntry>()
private val cachePath = project.getProjectDataPath("com.jonathanxd.dracon")
private val cacheFile = cachePath.resolve("log.obj")
init {
Files.createDirectories(cachePath)
if (Files.exists(this.cacheFile)) {
try {
this.map.putAll(this.cacheFile.toPijulLogEntriesMap())
} catch (e: Throwable) {
Files.delete(this.cacheFile)
}
}
}
fun queryOrCompute(hash: String, compute: () -> PijulOperationResult<PijulLogEntry>): PijulOperationResult<PijulLogEntry> {
return if (!this.map.containsKey(hash)) {
val computeEntry = compute()
if (computeEntry.statusCode !is SuccessStatusCode) {
return computeEntry
}
this.map[hash] = computeEntry.result!!
this.cacheFile.createLogEntriesPersist(this.map)
computeEntry
} else {
PijulOperationResult("change-$hash", SuccessStatusCode, this.map[hash])
}
}
}
@file:Suppress("UNCHECKED_CAST")
package com.github.jonathanxd.dracon.cache
import com.github.jonathanxd.dracon.pijul.PijulExecution
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
/**
* Some changes are so big that it takes a long time to retrieve, refreshing the view would cause
* the entire change tree to be retrieved again from Pijul, taking a long time again, this cache tries to alleviate
* this by caching the result of `Pijul` command.
*
* It in fact, does not cache the result of `pijul` command, it could cache anything, however an update function
* must be provided when querying the cache. The update function must be a pure function and the function identity
* must be tied to the `key`, in other words, the `key` is the identity of the function. This constraint must never be broken,
* because [PijulCache] does no update stored functions, the function is stored only when there is no cache data for the `key`,
* subsequent calls will ignore the provided function.
*/
interface PijulCache {
/**
* Retrieves cached value, or computes if absent.
*/
fun <K: Any, V: Any> cache(key: K, updateFn: () -> V): V
/**
* Request the cache to be updated, it does not mean that the cache will be updated in the time this is invoked.
*/
fun <K: Any> requestUpdate(key: K)
}
class HashMapCache : PijulCache {
private val cache = ConcurrentHashMap<Any, Cache<*>>()
private val updateExecutor = Executors.newCachedThreadPool()
override fun <K: Any, V: Any> cache(key: K, updateFn: () -> V): V {
return (this.cache.computeIfAbsent(key) {
val update = updateFn()
Cache(update, updateFn)
} as Cache<V>).value
}
override fun <K : Any> requestUpdate(key: K) {
val fn = this.cache[key]?.updateFn
if (fn != null) {
updateExecutor.submit {
val updated = fn()
this.cache[key] = Cache(updated, fn)
}
}
}
}
data class HashKey(val hash: String)
data class Cache<V: Any>(var value: V, var updateFn: (() -> V)?)
data class CommandResult(val stdOut: String, val stdErr: String)