diff --git a/.idea/compiler.xml b/.idea/compiler.xml index b589d56e9..b86273d94 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d26dee585..47e6a3606 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -220,7 +220,7 @@ dependencies { } // JSON Parser // Torrent Support - implementation(libs.torrentserver) + // implementation(libs.torrentserver) // Downloading & Networking implementation(libs.work.runtime) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aacce0393..1a0b514a2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,6 @@ - - diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/Torrent.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/Torrent.kt index 391d4f586..0f9460e78 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/Torrent.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/Torrent.kt @@ -8,6 +8,7 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkType +import go.Seq import torrServer.TorrServer import java.io.File import java.net.ConnectException @@ -201,6 +202,8 @@ object Torrent { * FYI this will throw a os.Exit(1) if port is taken and is not currently checked, * so if someone complains then add an extra check. */ private suspend fun setup(dir: String): Boolean { + Seq.load() + if (echo()) { return true } diff --git a/app/src/main/java/go/Seq.java b/app/src/main/java/go/Seq.java new file mode 100644 index 000000000..2a510bdca --- /dev/null +++ b/app/src/main/java/go/Seq.java @@ -0,0 +1,413 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package go; + +import android.content.Context; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.HashSet; +import java.util.logging.Logger; + +import torrServer.TorrServer; + +// Seq is a sequence of machine-dependent encoded values. +// Used by automatically generated language bindings to talk to Go. +public class Seq { + private static Logger log = Logger.getLogger("GoSeq"); + + // also known to bind/seq/ref.go and bind/objc/seq_darwin.m + private static final int NULL_REFNUM = 41; + + // use single Ref for null Object + public static final Ref nullRef = new Ref(NULL_REFNUM, null); + + // The singleton GoRefQueue + private static GoRefQueue goRefQueue = null; + + static Boolean hasLoaded = false; + + // We want to lazy loadLibrary in case of crashes + public static synchronized void load() { + if (hasLoaded) return; + hasLoaded = true; + log.info("Starting loading gojni"); + System.loadLibrary("gojni"); + log.info("Starting loading gojni init"); + init(); + log.info("Starting loading gojni goRefQueue"); + goRefQueue = new GoRefQueue(); + log.info("Starting loading gojni Universe"); + Universe.touch(); + Universe._init(); + log.info("Starting loading gojni TorrServer"); + TorrServer.touch(); + TorrServer._init(); + log.info("Loaded torrent server"); + } + + // setContext sets the context in the go-library to be used in RunOnJvm. + public static void setContext(Context context) { + setContext((java.lang.Object)context); + } + + private static native void init(); + + // Empty method to run class initializer + public static void touch() {} + + private Seq() { + } + + // ctx is an android.context.Context. + static native void setContext(java.lang.Object ctx); + + public static void incRefnum(int refnum) { + tracker.incRefnum(refnum); + } + + // incRef increments the reference count of Java objects. + // For proxies for Go objects, it calls into the Proxy method + // incRefnum() to make sure the Go reference count is positive + // even if the Proxy is garbage collected and its Ref is finalized. + public static int incRef(Object o) { + return tracker.inc(o); + } + + public static int incGoObjectRef(GoObject o) { + return o.incRefnum(); + } + + // trackGoRef tracks a Go reference and decrements its refcount + // when the given GoObject wrapper is garbage collected. + // + // TODO(crawshaw): We could cut down allocations for frequently + // sent Go objects by maintaining a map to weak references. This + // however, would require allocating two objects per reference + // instead of one. It also introduces weak references, the bane + // of any Java debugging session. + // + // When we have real code, examine the tradeoffs. + public static void trackGoRef(int refnum, GoObject obj) { + if (refnum > 0) { + throw new RuntimeException("trackGoRef called with Java refnum " + refnum); + } + goRefQueue.track(refnum, obj); + } + + public static Ref getRef(int refnum) { + return tracker.get(refnum); + } + + // Increment the Go reference count before sending over a refnum. + // The ref parameter is only used to make sure the referenced + // object is not garbage collected before Go increments the + // count. It's the equivalent of Go's runtime.KeepAlive. + public static native void incGoRef(int refnum, GoObject ref); + + // Informs the Go ref tracker that Java is done with this refnum. + static native void destroyRef(int refnum); + + // decRef is called from seq.FinalizeRef + static void decRef(int refnum) { + tracker.dec(refnum); + } + + // A GoObject is a Java class implemented in Go. When a GoObject + // is passed to Go, it is wrapped in a Go proxy, to make it behave + // the same as passing a regular Java class. + public interface GoObject { + // Increment refcount and return the refnum of the proxy. + // + // The Go reference count need to be bumped while the + // refnum is passed to Go, to avoid finalizing and + // invalidating it before being translated on the Go side. + int incRefnum(); + } + // A Proxy is a Java object that proxies a Go object. Proxies, unlike + // GoObjects, are unwrapped to their Go counterpart when deserialized + // in Go. + public interface Proxy extends GoObject {} + + // A Ref represents an instance of a Java object passed back and forth + // across the language boundary. + public static final class Ref { + public final int refnum; + + private int refcnt; // Track how many times sent to Go. + + public final Object obj; // The referenced Java obj. + + Ref(int refnum, Object o) { + if (refnum < 0) { + throw new RuntimeException("Ref instantiated with a Go refnum " + refnum); + } + this.refnum = refnum; + this.refcnt = 0; + this.obj = o; + } + + void inc() { + // Count how many times this ref's Java object is passed to Go. + if (refcnt == Integer.MAX_VALUE) { + throw new RuntimeException("refnum " + refnum + " overflow"); + } + refcnt++; + } + } + + static final RefTracker tracker = new RefTracker(); + + static final class RefTracker { + private static final int REF_OFFSET = 42; + + // Next Java object reference number. + // + // Reference numbers are positive for Java objects, + // and start, arbitrarily at a different offset to Go + // to make debugging by reading Seq hex a little easier. + private int next = REF_OFFSET; // next Java object ref + + // Java objects that have been passed to Go. refnum -> Ref + // The Ref obj field is non-null. + // This map pins Java objects so they don't get GCed while the + // only reference to them is held by Go code. + private final RefMap javaObjs = new RefMap(); + + // Java objects to refnum + private final IdentityHashMap javaRefs = new IdentityHashMap<>(); + + // inc increments the reference count of a Java object when it + // is sent to Go. inc returns the refnum for the object. + synchronized int inc(Object o) { + if (o == null) { + return NULL_REFNUM; + } + if (o instanceof Proxy) { + return ((Proxy)o).incRefnum(); + } + Integer refnumObj = javaRefs.get(o); + if (refnumObj == null) { + if (next == Integer.MAX_VALUE) { + throw new RuntimeException("createRef overflow for " + o); + } + refnumObj = next++; + javaRefs.put(o, refnumObj); + } + int refnum = refnumObj; + Ref ref = javaObjs.get(refnum); + if (ref == null) { + ref = new Ref(refnum, o); + javaObjs.put(refnum, ref); + } + ref.inc(); + return refnum; + } + + synchronized void incRefnum(int refnum) { + Ref ref = javaObjs.get(refnum); + if (ref == null) { + throw new RuntimeException("referenced Java object is not found: refnum="+refnum); + } + ref.inc(); + } + + // dec decrements the reference count of a Java object when + // Go signals a corresponding proxy object is finalized. + // If the count reaches zero, the Java object is removed + // from the javaObjs map. + synchronized void dec(int refnum) { + if (refnum <= 0) { + // We don't keep track of the Go object. + // This must not happen. + log.severe("dec request for Go object "+ refnum); + return; + } + if (refnum == Seq.nullRef.refnum) { + return; + } + // Java objects are removed on request of Go. + Ref obj = javaObjs.get(refnum); + if (obj == null) { + throw new RuntimeException("referenced Java object is not found: refnum="+refnum); + } + obj.refcnt--; + if (obj.refcnt <= 0) { + javaObjs.remove(refnum); + javaRefs.remove(obj.obj); + } + } + + // get returns an existing Ref to a Java object. + synchronized Ref get(int refnum) { + if (refnum < 0) { + throw new RuntimeException("ref called with Go refnum " + refnum); + } + if (refnum == NULL_REFNUM) { + return nullRef; + } + Ref ref = javaObjs.get(refnum); + if (ref == null) { + throw new RuntimeException("unknown java Ref: "+refnum); + } + return ref; + } + } + + // GoRefQueue is a queue of GoRefs that are no longer live. An internal thread + // processes the queue and decrement the reference count on the Go side. + static class GoRefQueue extends ReferenceQueue { + // The set of tracked GoRefs. If we don't hold on to the GoRef instances, the Java GC + // will not add them to the queue when their referents are reclaimed. + private final Collection refs = Collections.synchronizedCollection(new HashSet()); + + void track(int refnum, GoObject obj) { + refs.add(new GoRef(refnum, obj, this)); + } + + GoRefQueue() { + Thread daemon = new Thread(new Runnable() { + @Override public void run() { + while (true) { + try { + GoRef ref = (GoRef)remove(); + refs.remove(ref); + destroyRef(ref.refnum); + ref.clear(); + } catch (InterruptedException e) { + // Ignore + } + } + } + }); + daemon.setDaemon(true); + daemon.setName("GoRefQueue Finalizer Thread"); + daemon.start(); + } + } + + // A GoRef is a PhantomReference to a Java proxy for a Go object. + // GoRefs are enqueued to the singleton GoRefQueue when no longer live, + // so the corresponding reference count can be decremented. + static class GoRef extends PhantomReference { + final int refnum; + + GoRef(int refnum, GoObject obj, GoRefQueue q) { + super(obj, q); + if (refnum > 0) { + throw new RuntimeException("GoRef instantiated with a Java refnum " + refnum); + } + this.refnum = refnum; + } + } + + // RefMap is a mapping of integers to Ref objects. + // + // The integers can be sparse. In Go this would be a map[int]*Ref. + static final class RefMap { + private int next = 0; + private int live = 0; + private int[] keys = new int[16]; + private Ref[] objs = new Ref[16]; + + RefMap() {} + + Ref get(int key) { + int i = Arrays.binarySearch(keys, 0, next, key); + if (i >= 0) { + return objs[i]; + } + return null; + } + + void remove(int key) { + int i = Arrays.binarySearch(keys, 0, next, key); + if (i >= 0) { + if (objs[i] != null) { + objs[i] = null; + live--; + } + } + } + + void put(int key, Ref obj) { + if (obj == null) { + throw new RuntimeException("put a null ref (with key "+key+")"); + } + int i = Arrays.binarySearch(keys, 0, next, key); + if (i >= 0) { + if (objs[i] == null) { + objs[i] = obj; + live++; + } + if (objs[i] != obj) { + throw new RuntimeException("replacing an existing ref (with key "+key+")"); + } + return; + } + if (next >= keys.length) { + grow(); + i = Arrays.binarySearch(keys, 0, next, key); + } + i = ~i; + if (i < next) { + // Insert, shift everything afterwards down. + System.arraycopy(keys, i, keys, i+1, next-i); + System.arraycopy(objs, i, objs, i+1, next-i); + } + keys[i] = key; + objs[i] = obj; + live++; + next++; + } + + private void grow() { + // Compact and (if necessary) grow backing store. + int[] newKeys; + Ref[] newObjs; + int len = 2*roundPow2(live); + if (len > keys.length) { + newKeys = new int[keys.length*2]; + newObjs = new Ref[objs.length*2]; + } else { + newKeys = keys; + newObjs = objs; + } + + int j = 0; + for (int i = 0; i < keys.length; i++) { + if (objs[i] != null) { + newKeys[j] = keys[i]; + newObjs[j] = objs[i]; + j++; + } + } + for (int i = j; i < newKeys.length; i++) { + newKeys[i] = 0; + newObjs[i] = null; + } + + keys = newKeys; + objs = newObjs; + next = j; + + if (live != next) { + throw new RuntimeException("bad state: live="+live+", next="+next); + } + } + + private static int roundPow2(int x) { + int p = 1; + while (p < x) { + p *= 2; + } + return p; + } + } +} diff --git a/app/src/main/java/go/Universe.java b/app/src/main/java/go/Universe.java new file mode 100644 index 000000000..364d7ef64 --- /dev/null +++ b/app/src/main/java/go/Universe.java @@ -0,0 +1,31 @@ +package go; + +public abstract class Universe { + private Universe() { + } + + public static void touch() { + } + + public static native void _init(); + + private static final class proxyerror extends Exception implements Seq.Proxy, error { + private final int refnum; + + public final int incRefnum() { + Seq.incGoRef(this.refnum, this); + return this.refnum; + } + + proxyerror(int var1) { + this.refnum = var1; + Seq.trackGoRef(var1, this); + } + + public String getMessage() { + return this.error(); + } + + public native String error(); + } +} diff --git a/app/src/main/java/go/error.java b/app/src/main/java/go/error.java new file mode 100644 index 000000000..39ba8e767 --- /dev/null +++ b/app/src/main/java/go/error.java @@ -0,0 +1,5 @@ +package go; + +public interface error { + String error(); +} \ No newline at end of file diff --git a/app/src/main/java/torrServer/TorrServer.java b/app/src/main/java/torrServer/TorrServer.java new file mode 100644 index 000000000..085e2732c --- /dev/null +++ b/app/src/main/java/torrServer/TorrServer.java @@ -0,0 +1,18 @@ +package torrServer; + +public abstract class TorrServer { + private TorrServer() { + } + + public static void touch() { + } + public static native void _init(); + + public static native void addTrackers(String var0); + + public static native long startTorrentServer(String var0, long var1); + + public static native void stopTorrentServer(); + + public static native void waitTorrentServer(); +} diff --git a/app/src/main/jniLibs/arm64-v8a/libgojni.so b/app/src/main/jniLibs/arm64-v8a/libgojni.so new file mode 100644 index 000000000..7284f5cde Binary files /dev/null and b/app/src/main/jniLibs/arm64-v8a/libgojni.so differ diff --git a/app/src/main/jniLibs/armeabi-v7a/libgojni.so b/app/src/main/jniLibs/armeabi-v7a/libgojni.so new file mode 100644 index 000000000..f1067546c Binary files /dev/null and b/app/src/main/jniLibs/armeabi-v7a/libgojni.so differ diff --git a/app/src/main/jniLibs/x86/libgojni.so b/app/src/main/jniLibs/x86/libgojni.so new file mode 100644 index 000000000..fcb10e9a9 Binary files /dev/null and b/app/src/main/jniLibs/x86/libgojni.so differ diff --git a/app/src/main/jniLibs/x86_64/libgojni.so b/app/src/main/jniLibs/x86_64/libgojni.so new file mode 100644 index 000000000..f3dcfeb2e Binary files /dev/null and b/app/src/main/jniLibs/x86_64/libgojni.so differ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cb457bbe5..8f08fad4f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,7 +40,7 @@ safefile = "0.0.8" shimmer = "0.5.0" swiperefreshlayout = "1.1.0" tmdbJava = "2.11.0" -torrentserver = "883c17942d" +#torrentserver = "883c17942d" tvprovider = "1.0.0" video = "1.0.0" workRuntime = "2.10.0" @@ -105,7 +105,7 @@ safefile = { module = "com.github.LagradOst:SafeFile", version.ref = "safefile" shimmer = { module = "com.facebook.shimmer:shimmer", version.ref = "shimmer" } swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" } tmdb-java = { module = "com.uwetrottmann.tmdb2:tmdb-java", version.ref = "tmdbJava" } -torrentserver = { module = "com.github.recloudstream:torrentserver", version.ref = "torrentserver" } +#torrentserver = { module = "com.github.recloudstream:torrentserver", version.ref = "torrentserver" } tvprovider = { module = "androidx.tvprovider:tvprovider", version.ref = "tvprovider" } video = { module = "com.google.android.mediahome:video", version.ref = "video" } work-runtime = { module = "androidx.work:work-runtime", version.ref = "workRuntime" }