// Layer 2 (swarm / torrent / sync) verification and run tasks. // Kept out of build.gradle so the main build stays readable. // ./gradlew :thoughtframe:twoInstanceCheck (the Layer 2 lock) // ./gradlew :thoughtframe:cloneCheck | dedupCheck | v2Check | ... def swarmLibsDir = layout.buildDirectory.dir('swarm-libs') // libtorrent4j 2.x expects its native on java.library.path; pull it out of the platform jar. tasks.register('extractSwarmNative', Copy) { group = 'verification' description = 'Extract libtorrent4j.so from the platform jar for java.library.path.' from({ configurations.runtimeClasspath.files .findAll { it.name.startsWith('libtorrent4j-linux') } .collect { zipTree(it) } }) include 'lib/x86_64/*.so' eachFile { it.path = it.name } includeEmptyDirs = false into swarmLibsDir } // Headless smoke test: can a libtorrent session start? (backs the torrent:// transport client) tasks.register('swarmCheck', JavaExec) { group = 'verification' description = 'Start a libtorrent session to verify the native BitTorrent library loads.' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.SwarmCheck' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // Two nodes: A seeds a folder, B pulls it over the swarm — watch B's folder fill. tasks.register('twoNodeCheck', JavaExec) { group = 'verification' description = 'Seed a folder in node A and pull it into node B over BitTorrent.' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.TorrentTwoNodeCheck' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // The full loop: publish a repo to a torrent, seed it, clone torrent:// from a 2nd node. tasks.register('cloneCheck', JavaExec) { group = 'verification' description = 'Publish a repo as a torrent, seed it, and clone it back over torrent://.' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.TorrentCloneCheck' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // Same, but node A and node B are two separate JVM processes. tasks.register('twoProcessCheck', JavaExec) { group = 'verification' description = 'Seed in one JVM and clone in a second JVM over BitTorrent.' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.TwoProcessCheck' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // Run the torrent publish/seed/pull pages inside the engine's own webapp (thoughtframe/webapp). tasks.register('torrentWeb', JavaExec) { group = 'application' description = 'Serve the engine webapp with the torrent pages at /thoughtframe/torrent.html.' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.TorrentWebApp' workingDir rootProject.projectDir standardInput = System.in jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" if (project.hasProperty('demo')) { args = ['demo'] } } // Boot the engine webapp, GET /thoughtframe/torrent.html once, and exit (CI-friendly). tasks.register('torrentWebCheck', JavaExec) { group = 'verification' description = 'Boot the engine webapp and verify /thoughtframe/torrent.html serves.' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.TorrentWebApp' workingDir rootProject.projectDir jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" args = ['check'] } // Drive node A by hand (terminal 1): build + publish + seed a demo repo, print the infohash, hold. tasks.register('seedNode', JavaExec) { group = 'application' description = 'Node A: build a repo, publish + seed it, print the infohash, and stay online.' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.TorrentSeedMain' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" args = [layout.buildDirectory.dir('nodeA').get().asFile.absolutePath] } // Drive node B by hand (terminal 2): clone the infohash node A printed. // ./gradlew :thoughtframe:pullNode -Pinfohash= tasks.register('pullNode', JavaExec) { group = 'application' description = 'Node B: clone torrent:// into build/nodeB. Pass -Pinfohash=.' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.TorrentPullMain' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" // Resolve args at execution time: configuration runs on every IDE sync, where // -Pinfohash is absent, so project.property('infohash') here would fail the build. doFirst { if (!project.hasProperty('infohash')) { throw new GradleException('Pass -Pinfohash= (printed by :thoughtframe:seedNode)') } args = [project.property('infohash'), layout.buildDirectory.dir('nodeB').get().asFile.absolutePath] } } // Does a second mutable DHT put update the value? (BEP 44 seq question.) tasks.register('dhtUpdateProbe', JavaExec) { group = 'verification' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.DhtUpdateProbe' dependsOn 'extractSwarmNative' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // Learn the DHT mutable-item API: put(public, secret, entry, salt) / get(public, salt, timeout). tasks.register('dhtProbe', JavaExec) { group = 'verification' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.DhtProbe' dependsOn 'extractSwarmNative' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // Figure out libtorrent4j Ed25519 arg order. tasks.register('ed25519Probe', JavaExec) { group = 'verification' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.Ed25519Probe' dependsOn 'extractSwarmNative' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // Launch several peers as separate JVM processes (real peers) for fan-out testing. tasks.register('peers', JavaExec) { group = 'application' description = 'Start alpha/beta/gamma/delta as separate processes (9091-9094).' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.sync.ui.PeersLauncher' standardInput = System.in jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // Launch two Layer-1 nodes (9091/9092) to edit, publish and receive visually. tasks.register('syncUi', JavaExec) { group = 'application' description = 'Start two sync nodes at http://127.0.0.1:9091 and :9092 (DHT naming).' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.sync.ui.SyncUiMain' standardInput = System.in jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // Fully decentralized: two instances resolve each other through the BitTorrent DHT (no shared dir). tasks.register('dhtTwoInstanceCheck', JavaExec) { group = 'verification' description = 'Two instances find each other via the DHT (public key = address, BEP 44 head).' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.sync.DhtTwoInstanceCheck' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // LOCK Layer 2: two instances, each a folder, publish + view each other's posts through Sync only. tasks.register('twoInstanceCheck', JavaExec) { group = 'verification' description = 'Two instances publish and view each other through the org.thoughtframe.sync facade.' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.sync.TwoInstanceCheck' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // Prove v2 file-level dedup: pull snapshot 1, then snapshot 2 as a different torrent; measure bytes. tasks.register('dedupCheck', JavaExec) { group = 'verification' description = 'Prove v2 file dedup across torrents (loose-object payload, two snapshots).' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.DedupCheck' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // Dual addressing: hybrid torrent read as btih + btmh, fetched via a magnet carrying both. tasks.register('v2Check', JavaExec) { group = 'verification' description = 'Publish hybrid; address by btih AND btmh; fetch via a dual magnet.' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.V2Check' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // Probe: does the builder emit v1, v2, or hybrid, and can we read the v2 hash? tasks.register('v2Probe', JavaExec) { group = 'verification' description = 'Inspect what TorrentBuilder emits (v1/v2/hybrid).' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.V2Probe' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // The design in miniature: a Git "feed" (posts.jsonl) distributed as immutable delta units over a // swarm, named by a mutable head; followers pull only the deltas they lack. tasks.register('feedCheck', JavaExec) { group = 'verification' description = 'Feed = Git repo of posts; delta units over a swarm; mutable head.' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.feed.FeedCheck' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // Composite scenario: node A seeds an image + a repo whose index.json carries its magnet; node B // clones the repo, reads the JSON, and follows the magnet to pull the image. tasks.register('compositeCheck', JavaExec) { group = 'verification' description = 'Git-over-BitTorrent index -> magnet -> image, across two JVMs.' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.CompositeCheck' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" } // Pull from the running engine: clone the repo, read index.json, follow the magnet to the image. // ./gradlew :thoughtframe:pullComposite -Prepoinfohash= tasks.register('pullComposite', JavaExec) { group = 'application' description = 'Clone a repo over the swarm and follow index.json\u0027s magnet to the image.' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.CompositePull' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" args = [project.findProperty('repoinfohash') ?: '', layout.buildDirectory.dir('composite').get().asFile.absolutePath] } // Isolate the in-engine fetch crash: who fetches (separate session vs the seeder) and on which thread. tasks.register('pullIsolate', JavaExec) { group = 'verification' description = 'Reproduce the torrent fetch in isolation. -Pmode=main|thread|same' dependsOn 'extractSwarmNative' classpath = sourceSets.main.runtimeClasspath mainClass = 'org.thoughtframe.git.torrent.libtorrent.PullIsolate' jvmArgs "-Djava.library.path=${swarmLibsDir.get().asFile.absolutePath}" args = [project.findProperty('mode') ?: 'main'] }