Documentation and cache performance update

[?]
Sep 22, 2021, 5:02 PM
7M2QIAYK7RM2YDZ3DZMYNJQH26H4676VBINAW3VSBMJQJE4RI4MQC

Dependencies

  • [2] AQMU4WCO typo fix
  • [3] FSXHAVRF Update for IntelliJ 2021.2.2
  • [4] Q35OTML2 Remove use of coroutines, which was blocking IntelliJ UI in larger repositories Improvements for bigger repositories, now Dracon caches the changes that happened in a revision in a file, so everytime Dracon needs to query the changes of a revision, it loads directly from memory instead of doing a full-scan in Pijul repository. For tiny projects it is not a problem, but for medium ones it takes more than five minutes to scan the entire repository (and it was tested with a repo of only 700 records, however there was changes that had more than 60.000 lines). The cache file is saved in IntelliJ Data Path (project specific) and is compressed with gzip, so it will not use so much disk space (the cost worths the gains).
  • [5] 37OJKSWJ Improved caching code a lot
  • [6] 4TGL4RKF Fix concurrent pijul execution locking each other
  • [7] QXUEMZ3B Initial CahngeProvider
  • [8] MZYZIVHY First experimental build, it seems like it is breaking Git plugin, however, it still something =D
  • [9] FRFFQV7V Basic show history support.
  • [10] ISO7J5ZH More caches, better and generic cache code. Now Dracon listen to file changes to drop cached data. Implemented caches: - File contents in specific revision (never dropped) - Pijul ls and Pijul diff results - File Revision and File changes by patch - some others.. Dracon is incredible fast now, but still will take some time for bigger repos.
  • [11] VBL5BQH7 Experimental
  • [12] 2N67RQZC Add auto installation support and cache content of ContentRevision
  • [13] Q7FXTHVU First record support, YEAAAH, RECOOORD
  • [14] 7L5LODGZ Parse changes from `pijul change`
  • [*] FNNW5IEA Added more plugin files to Pijul
  • [*] GGYFPXND Initial plugin
  • [*] EAGIDXOL Build 2 to only listen to changes in project under pijul

Change contents

  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/revision/PijulVcsFileRevision.kt" at line 30
    [4.3451][4.3451:3500]()
    class PijulVcsFileRevision(val project: Project,
    [4.3451]
    [4.3500]
    class PijulVcsFileRevision constructor(val project: Project,
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulRecordDetails.kt" at line 25
    [4.19166][4.19166:19259]()
    project, entry.id, emptyList(), entry.commitTime, rootFile, entry.subject, entry.author,
    [4.19166]
    [4.19259]
    project, entry.id, emptyList() /*entry.getPijulParents()*/, entry.commitTime, rootFile, entry.subject, entry.author,
  • edit in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLogProvider.kt" at line 343
    [4.32998]
    [4.32998]
    // Use empty parents since Pijul hierarchy is not linear
    // this leads to a problem shown in the [pijul-log-tree.png] file.
    val parents = emptyList<Hash>()//it.parents
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLogProvider.kt" at line 349
    [4.33105][4.33105:33142]()
    emptyList(),
    [4.33105]
    [4.33142]
    parents,
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 134
    [4.62614][4.62614:62662]()
    val path: String,
    val rootPath: String,
    [4.62614]
    [4.62662]
    val path: Filename,
    val rootPath: FileDir,
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 145
    [4.4072][4.4072:4304]()
    this.rootPath == "/" -> this.path
    this.rootPath.endsWith("/") -> this.rootPath + this.path
    this.rootPath.isEmpty() -> this.path
    else -> this.rootPath + "/" + this.path
    [4.4072]
    [4.4304]
    this.rootPath.dir == "/" -> this.path.name
    this.rootPath.dir.endsWith("/") -> (this.rootPath + this.path).fullPath
    this.rootPath.dir.isEmpty() -> this.path.name
    else -> (this.rootPath + this.path).fullPath
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 159
    [4.62937][4.62937:62959]()
    val path: String,
    [4.62937]
    [4.62959]
    val path: FilePath,
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 168
    [4.14181][4.14181:14207]()
    get() = this.path
    [4.14181]
    [4.5558]
    get() = this.path.fullPath
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 179
    [4.63250][4.63250:63272]()
    val path: String,
    [4.63250]
    [4.63272]
    val path: FilePath,
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 188
    [4.14274][4.14274:14300]()
    get() = this.path
    [4.14274]
    [4.5643]
    get() = this.path.fullPath
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 198
    [4.63550][4.63550:63594]()
    val older: String,
    val new: String,
    [4.63550]
    [3.43014]
    val older: FilePath,
    val new: FilePath,
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 206
    [4.14367][4.14367:14392]()
    get() = this.new
    [4.14367]
    [4.5728]
    get() = this.new.fullPath
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 216
    [4.63815][4.63815:63837]()
    val path: String,
    [4.63815]
    [4.63837]
    val path: FilePath,
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 226
    [4.14459][4.14459:14485]()
    get() = this.path
    [4.14459]
    [4.5813]
    get() = this.path.fullPath
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 236
    [4.64148][4.64148:64170]()
    val path: String,
    [4.64148]
    [4.64170]
    val path: FilePath,
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 246
    [4.14552][4.14552:14578]()
    get() = this.path
    [4.14552]
    [4.5898]
    get() = this.path.fullPath
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 256
    [4.64459][4.64459:64481]()
    val path: String,
    [4.64459]
    [4.64481]
    val path: FilePath,
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 264
    [4.14645][4.14645:14671]()
    get() = this.path
    [4.14645]
    [4.5983]
    get() = this.path.fullPath
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 274
    [4.64750][4.64750:64772]()
    val path: String,
    [4.64750]
    [4.64772]
    val path: FilePath,
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 283
    [4.14738][4.14738:14764]()
    get() = this.path
    [4.14738]
    [4.6068]
    get() = this.path.fullPath
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 293
    [4.65095][4.65095:65117]()
    val path: String,
    [4.65095]
    [4.65117]
    val path: FilePath,
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 302
    [4.14831][4.14831:14857]()
    get() = this.path
    [4.14831]
    [4.6153]
    get() = this.path.fullPath
  • edit in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 406
    [4.68031][4.68031:68143]()
    val HUNK_ZOMBIE_PATTERN = Regex("(\\d+)\\. Resurrecting zombie lines in (\"[^\"]+\"):(\\d+) (\\d+\\.\\d+)\\n?")
  • edit in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 407
    [4.68263][4.68263:68309]()
    val CHANGE_PATTERN = Regex("([-+]) (.*)\\n?")
  • edit in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 409
    [4.68310]
    [4.68310]
    // Corner case: :D 9.1256 -> 4.12200:12305/9, B:BD 4.12200 -> 4.12200:12305/4
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 413
    [4.68416][4.68416:68472]()
    val FLAG_PATTERN = Regex("([BFD]+:[BFD]+ [^\\n]+)\\n?")
    [4.68416]
    [4.2676]
    val FLAG_PATTERN = Regex("(([BFD]+:[BFD]+ [^\\n]+)|(:[BFD]+ [^\\n]+))\\n?")
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 507
    [3.43567][3.43567:43653]()
    val fileOrPath = add.fileName
    val rootPath = add.path
    [3.43567]
    [3.43653]
    val fileOrPath = add.fileName.parseFilename()
    val rootPath = add.path.parseFileDir()
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 535
    [3.44038][3.44038:44081]()
    val path = remove.fileName
    [3.44038]
    [3.44081]
    val path = remove.fileName.parseFilePath()
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 563
    [3.44449][3.44449:44491]()
    val path = undel.fileName
    [3.44449]
    [3.44491]
    val path = undel.fileName.parseFilePath()
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 591
    [3.44854][3.44854:44924]()
    val new = move.new
    val old = move.old
    [3.44854]
    [3.44924]
    val new = move.new.parseFilePath()
    val old = move.old.parseFilePath()
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 618
    [3.45256][3.45256:45293]()
    val path = edit.file
    [3.45256]
    [3.45293]
    val path = edit.file.parseFilePath()
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 651
    [3.45721][3.45721:45769]()
    val path = replacement.fileName
    [3.45721]
    [3.45769]
    val path = replacement.fileName.parseFilePath()
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 685
    [3.46267][3.46267:46316]()
    val path = nameConflict.fileName
    [3.46267]
    [3.46316]
    val path = nameConflict.fileName.parseFilePath()
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 715
    [3.46705][3.46705:46755]()
    val path = orderConflict.fileName
    [3.46705]
    [3.46755]
    val path = orderConflict.fileName.parseFilePath()
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 748
    [3.47069][3.47069:47117]()
    val path = zombieLines.fileName
    [3.47069]
    [3.47117]
    val path = zombieLines.fileName.parseFilePath()
  • edit in "src/main/kotlin/com/github/jonathanxd/dracon/log/PijulLog.kt" at line 755
    [4.80618]
    [4.80618]
  • edit in "src/main/kotlin/com/github/jonathanxd/dracon/log/Patterns.kt" at line 19
    [3.50637]
    [3.50637]
    // Corner case: 120. File addition: "coroutine" in "src/main/kotlin/com/github/jonathanxd/dracon" +dx "binary"
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/Patterns.kt" at line 21
    [3.50691][3.50691:50839]()
    Regex("(?<changeNumber>\\d+)\\. File addition: (\"(?<file>.*)(?:\" )+)in (\"(?<path>.*)(?:\" )+)(\"(?<encoding>[^\"]+)\")(?<meta> [^ ]+)?\\n?")
    [3.50691]
    [3.50839]
    Regex("(?<changeNumber>\\d+)\\. File addition: (\"(?<file>.*)(?:\" )+)in (\"(?<path>.*)(?:\" )+)(?<mode>[^ ]+)?( ?\"(?<encoding>[^\"]+)\")(?<meta> [^ ]+)?\\n?")
  • edit in "src/main/kotlin/com/github/jonathanxd/dracon/log/Patterns.kt" at line 44
    [3.51756]
    [3.51756]
    // Corner case: 9. Moved: "src/main/kotlin/com/github/jonathanxd/dracon/handler" "handler" +dx 7.107
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/log/Patterns.kt" at line 46
    [3.51814][3.51814:51985]()
    Regex("(?<changeNumber>\\d+)\\. Moved: (\"(?<old>.*)(?:\" )+)(\"(?<new>.*)(?:\" )+)(?<inode>\\d+\\.\\d+)([^ ]+ )?( \"(?<encoding>[^\"]+)\")?([^ ]+ )?(?<meta>.*)\\n?")
    [3.51814]
    [3.51985]
    Regex("(?<changeNumber>\\d+)\\. Moved: (\"(?<old>.*)(?:\" )+)(\"(?<new>.*)(?:\" )+)((?<mode>[^ ]+) )?(?<inode>\\d+\\.\\d+)([^ ]+ )?( \"(?<encoding>[^\"]+)\")?([^ ]+ )?(?<meta>.*)\\n?")
  • edit in "src/main/kotlin/com/github/jonathanxd/dracon/log/Patterns.kt" at line 83
    [3.53605]
    [3.53605]
    val mode: String? get() = matchResult.groups["mode"]?.value
  • edit in "src/main/kotlin/com/github/jonathanxd/dracon/log/Patterns.kt" at line 108
    [3.54785]
    [3.54785]
    val mode: String? get() = matchResult.groups["mode"]?.value
  • file addition: Filename.kt (----------)
    [16.6847]
    package com.github.jonathanxd.dracon.log
    import com.github.jonathanxd.dracon.cmd.removeTrailingSlash
    @JvmInline
    value class Filename(val name: String)
    @JvmInline
    value class FileDir(val dir: String)
    @JvmInline
    value class FilePath(val fullPath: String)
    //\"src/test/kotlin/com/github/jonathanxd/dracon/test/DraconTest.kt\"
    fun String.parseFilePath(): FilePath = FilePath(this.unescape())
    fun String.parseFilename(): Filename = Filename(this.unescape())
    fun String.parseFileDir(): FileDir = FileDir(this.unescape())
    operator fun FileDir.plus(name: Filename) =
    FilePath(this.dir.resolve(name.name))
    operator fun FileDir.plus(name: FilePath) =
    FilePath(this.dir.resolve(name.fullPath))
    operator fun FilePath.plus(name: FilePath) =
    FilePath(this.fullPath.resolve(name.fullPath))
    fun String.resolve(other: String) =
    "${this.removePathTrailingSlash()}/${other.removePathLeadingSlash()}"
    const val ESCAPED_STRING_PREFIX = """\""""
    fun String.unescape(): String =
    if (this.startsWith(ESCAPED_STRING_PREFIX) && this.endsWith(ESCAPED_STRING_PREFIX))
    this.substring(ESCAPED_STRING_PREFIX.length, this.length - ESCAPED_STRING_PREFIX.length)
    else this
    private fun String.removePathTrailingSlash() =
    if (this.endsWith("/")) this.substring(0, this.lastIndex)
    else this
    private fun String.removePathLeadingSlash() =
    if (this.startsWith("/")) this.substring(1, this.length)
    else this
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/cache/PijulLogRevisionCache.kt" at line 29
    [4.27586][4.27586:27671]()
    override val cache = DataCache<String, PijulRevisionNumber>(project, "revision")
    [4.27586]
    [4.27671]
    override val cache = ActorDataCache<String, PijulRevisionNumber>(project, "revision")
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/cache/PijulLogEntryChangeCache.kt" at line 29
    [4.28578][4.28578:28660]()
    override val cache = DataCache<String, PijulLogEntry>(project, "path_change")
    [4.28578]
    [4.15343]
    override val cache = ActorDataCache<String, PijulLogEntry>(project, "path_change")
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/cache/PijulLogEntryCache.kt" at line 22
    [4.28946][4.28946:29023]()
    override val cache = DataCache<String, PijulLogEntry>(project, "change")
    [4.28946]
    [4.12218]
    override val cache = ActorDataCache<String, PijulLogEntry>(project, "change")
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/cache/FileStatusCache.kt" at line 30
    [4.29163][4.29163:29261]()
    override val cache = DataCache<String, CacheablePijulFileStatus>(this.project, "file_status")
    [4.29163]
    [4.17993]
    override val cache = ActorDataCache<String, CacheablePijulFileStatus>(this.project, "file_status")
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/cache/FileStatusCache.kt" at line 34
    [4.18032][4.18032:18072]()
    this.cache.lock()
    try {
    [4.18032]
    [4.18072]
    this.cache.withLock {
  • edit in "src/main/kotlin/com/github/jonathanxd/dracon/cache/FileStatusCache.kt" at line 54
    [4.18884][4.18884:18936]()
    } finally {
    this.cache.unlock()
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/cache/DataCache.kt" at line 32
    [4.32755][4.32755:32794]()
    const val DATA_CACHE_VERSION: Long = 2
    [4.32755]
    [4.22009]
    const val DATA_CACHE_VERSION: Long = 3
  • edit in "src/main/kotlin/com/github/jonathanxd/dracon/cache/DataCache.kt" at line 250
    [4.25794]
    [4.35089]
    // Do async, accept MISS while locked
    // cache storage is very slow due to compression
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/cache/DataCache.kt" at line 265
    [4.35212][4.35212:35269]()
    return if (version != version) {
    [4.35212]
    [4.35269]
    return if (version != this.version) {
  • replacement in "src/main/kotlin/com/github/jonathanxd/dracon/cache/CacheService.kt" at line 26
    [4.36482][4.36482:36513]()
    val cache: DataCache<K, V>
    [4.36482]
    [4.36513]
    val cache: ActorDataCache<K, V>
    /**
    * Executes an operation in [cache] using the **cache write lock**.
    *
    * This does not guarantee absolute lock over the file caching mechanism.
    */
    fun <R> withLock(f: () -> R): R =
    this.cache.withLock(f)
  • file addition: ActorDataCache.kt (----------)
    [4.8571]
    package com.github.jonathanxd.dracon.cache
    import com.github.jonathanxd.dracon.coroutine.DRACON_SCHEDULER
    import com.intellij.execution.process.mediator.util.blockingGet
    import com.intellij.openapi.project.Project
    import com.intellij.openapi.project.getProjectDataPath
    import kotlinx.coroutines.*
    import kotlinx.coroutines.channels.actor
    import kotlinx.coroutines.channels.trySendBlocking
    import java.io.ObjectInputStream
    import java.io.ObjectOutputStream
    import java.nio.file.Files
    import java.nio.file.Path
    import java.nio.file.StandardOpenOption
    import java.time.Instant
    import java.util.concurrent.ConcurrentHashMap
    import java.util.concurrent.Executors
    import java.util.concurrent.locks.ReentrantLock
    import java.util.zip.GZIPInputStream
    import java.util.zip.GZIPOutputStream
    import kotlin.concurrent.withLock
    /**
    * Denotes the current version of data cache, must be updated when data cache format changes.
    */
    const val ACTOR_DATA_CACHE_VERSION: Long = 3
    /**
    * Actor based data cache storage, with support to cache misses and falling back to default resolver,
    * this allows writes to on-file cached to lock without freezing IntelliJ IDEA (when request for data happens in UI-thread).
    */
    class ActorDataCache<K, V>(val project: Project,
    val name: String) {
    private val cachePath = project.getProjectDataPath("com.github.jonathanxd.dracon")
    private val cacheFile = cachePath.resolve("$name.gz")
    private val manager = ActorDataCacheManager<K, CachedValue<V>>(this.cacheFile, DATA_CACHE_VERSION)
    private val lock = ReentrantLock()
    private val updateExecutor = Executors.newSingleThreadExecutor()
    private val inMemory = ConcurrentHashMap<K, CachedValue<V>>()
    init {
    this.manager.load {
    inMemory.putAll(it)
    }
    }
    /**
    * Executes
    */
    fun <R> withLock(f: () -> R) = this.lock.withLock(f)
    fun unload(key: K) {
    this.lock.withLock {
    this.inMemory.remove(key)
    }
    writeToDisk()
    }
    fun queryOrLoad(key: K, compute: (K) -> V): V {
    return this.lock.withLock {
    if (!this.inMemory.containsKey(key)) {
    val computeEntry = CachedValue(Instant.now(), compute(key))
    this.inMemory[key] = computeEntry
    writeToDisk()
    computeEntry
    } else {
    this.inMemory[key]!!
    }.value
    }
    }
    fun <I> queryOrLoad(key: K,
    compute: (K) -> I,
    filter: (I) -> Boolean,
    iMapper: (I) -> V,
    vMapper: (V) -> I): I {
    return this.lock.withLock {
    if (!this.inMemory.containsKey(key)) {
    val computeEntry = compute(key)
    if (!filter(computeEntry)) {
    return computeEntry
    }
    this.inMemory[key] = CachedValue(Instant.now(), iMapper(computeEntry!!))
    writeToDisk()
    computeEntry
    } else {
    vMapper(this.inMemory[key]!!.value)
    }
    }
    }
    /**
    * This call completes asynchronously.
    */
    private fun writeToDisk() {
    this.manager.write(this.inMemory)
    }
    /**
    * @see CacheService.invalidate
    */
    fun invalidate() {
    this.lock.withLock {
    this.inMemory.clear()
    this.writeToDisk()
    }
    }
    /**
    * @see CacheService.invalidate
    */
    fun invalidate(key: K) {
    this.lock.withLock {
    this.inMemory.remove(key)
    this.writeToDisk()
    }
    }
    /**
    * @see CacheService.updateCache
    */
    fun updateCache(key: K, newValueCompute: () -> V) {
    val instant = Instant.now()
    this.lock.withLock {
    val inMemoryValue = this.inMemory[key]
    if (inMemoryValue == null || inMemoryValue.instant.isBefore(instant)) {
    this.inMemory[key] = CachedValue(instant, newValueCompute())
    }
    }
    }
    /**
    * @see CacheService.updateCache
    */
    fun updateCache(keys: List<K>, newValueCompute: (K) -> V) {
    val instant = Instant.now()
    this.lock.withLock {
    for (key in keys) {
    val inMemoryValue = this.inMemory[key]
    if (inMemoryValue == null || inMemoryValue.instant.isBefore(instant)) {
    this.inMemory[key] = CachedValue(instant, newValueCompute(key))
    }
    }
    }
    }
    }
    sealed class CacheCommand<K, V> {
    class CacheLoad<K, V>(val completion: CompletableDeferred<Map<K, V>?>): CacheCommand<K, V>()
    class CacheSave<K, V>(val data: Map<K, V>, val completion: CompletableDeferred<Unit>): CacheCommand<K, V>()
    }
    // Do async, accept MISS while locked
    // cache storage is very slow due to compression
    class ActorDataCacheManager<K, V>(val path: Path, val version: Long) {
    @Suppress("BlockingMethodInNonBlockingContext")
    private val loader = CoroutineScope(Dispatchers.IO).actor<CacheCommand<K, V>> {
    for (msg in channel) {
    when (msg) {
    is CacheCommand.CacheLoad<K, V> -> {
    msg.completion.complete(if (!Files.exists(path)) {
    null
    } else {
    try {
    Files.newInputStream(path).use { stream ->
    GZIPInputStream(stream).use { gz ->
    ObjectInputStream(gz).use { reader ->
    val version = reader.readLong()
    if (version != this@ActorDataCacheManager.version) {
    emptyMap()
    } else {
    reader.readObject() as Map<K, V>
    }
    }
    }
    }
    } catch (t: Throwable) {
    t.printStackTrace()
    Files.delete(path)
    null
    }
    })
    }
    is CacheCommand.CacheSave<K, V> -> {
    Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING).use { writer ->
    GZIPOutputStream(writer).use { compress ->
    ObjectOutputStream(compress).use { oos ->
    oos.writeLong(this@ActorDataCacheManager.version)
    oos.writeObject(msg.data)
    msg.completion.complete(Unit)
    }
    }
    }
    }
    }
    }
    }
    init {
    Files.createDirectories(this.path.parent)
    }
    fun load(onLoad: suspend (Map<K, V>) -> Unit) {
    CoroutineScope(DRACON_SCHEDULER).launch {
    val complete = CompletableDeferred<Map<K, V>?>()
    loader.send(CacheCommand.CacheLoad(complete))
    val load = complete.await()
    if (load != null) {
    onLoad(load)
    }
    }
    }
    fun write(data: Map<K, V>, onComplete: suspend () -> Unit = {}) {
    CoroutineScope(DRACON_SCHEDULER).launch {
    val complete = CompletableDeferred<Unit>()
    loader.send(CacheCommand.CacheSave(data, complete))
    complete.await()
    onComplete()
    }
    }
    }
  • file addition: pijul-no-log-tree.png (----------)
    [1.0]
  • file addition: pijul-log-tree.png (----------)
    [1.0]
  • file addition: pijul-log-tree-would-be.png (----------)
    [1.0]
  • replacement in "README.md" at line 31
    [4.48758][4.48758:48842]()
    Dracon is in a heavily experimental state, and it depends on latest IDEA EAP build.
    [4.48758]
    [4.48842]
    Dracon is in a heavily experimental state, and it depends on IntelliJ 212.5284.40.
  • edit in "README.md" at line 89
    [18.1213353]
    [2.0]
    ### Dracon log history does not show related records
    Dracon shows history like:
    ![](pijul-no-log-tree.png)
    Instead of something link:
    ![](pijul-log-tree-would-be.png)
    This is because the relation of Pijul patches are not linear, because of this, the following happens when
    we try to correctly build the relation between records:
    ![](pijul-log-tree.png)