mirror of
https://gitlab.com/foxixus/neomovies_mobile.git
synced 2025-10-27 22:38:50 +05:00
feat: Add TorrentEngine library and new API client
- Created complete TorrentEngine library module with LibTorrent4j - Full torrent management (add, pause, resume, remove) - Magnet link metadata extraction - File priority management (even during download) - Foreground service with persistent notification - Room database for state persistence - Reactive Flow API for UI updates - Integrated TorrentEngine with MainActivity via MethodChannel - addTorrent, getTorrents, pauseTorrent, resumeTorrent, removeTorrent - setFilePriority for dynamic file selection - Full JSON serialization for Flutter communication - Created new NeoMoviesApiClient for Go-based backend - Email verification flow (register, verify, resendCode) - Google OAuth support - Torrent search via RedAPI - Multiple player support (Alloha, Lumex, Vibix) - Enhanced reactions system (likes/dislikes) - All movies/TV shows endpoints - Updated dependencies and build configuration - Java 17 compatibility - Updated Kotlin coroutines to 1.9.0 - Fixed build_runner version conflict - Added torrentengine module to settings.gradle.kts - Added comprehensive documentation - TorrentEngine README with usage examples - DEVELOPMENT_SUMMARY with full implementation details - ProGuard rules for library This is a complete rewrite of torrent functionality as a reusable library.
This commit is contained in:
@@ -11,12 +11,12 @@ android {
|
||||
ndkVersion = "27.0.12077973"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
@@ -44,20 +44,16 @@ flutter {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// libtorrent4j для работы с торрентами
|
||||
implementation("org.libtorrent4j:libtorrent4j:2.1.0-35")
|
||||
implementation("org.libtorrent4j:libtorrent4j-android-arm64:2.1.0-35")
|
||||
implementation("org.libtorrent4j:libtorrent4j-android-arm:2.1.0-35")
|
||||
implementation("org.libtorrent4j:libtorrent4j-android-x86:2.1.0-35")
|
||||
implementation("org.libtorrent4j:libtorrent4j-android-x86_64:2.1.0-35")
|
||||
// TorrentEngine library module
|
||||
implementation(project(":torrentengine"))
|
||||
|
||||
// Kotlin Coroutines
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
|
||||
|
||||
// Gson для JSON сериализации
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
implementation("com.google.code.gson:gson:2.11.0")
|
||||
|
||||
// AndroidX libraries
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
|
||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package com.neo.neomovies_mobile
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.neomovies.torrentengine.TorrentEngine
|
||||
import com.neomovies.torrentengine.models.FilePriority
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
@@ -17,55 +18,219 @@ class MainActivity : FlutterActivity() {
|
||||
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
private val gson = Gson()
|
||||
private lateinit var torrentEngine: TorrentEngine
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
|
||||
// Initialize TorrentEngine
|
||||
torrentEngine = TorrentEngine.getInstance(applicationContext)
|
||||
torrentEngine.startStatsUpdater()
|
||||
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, TORRENT_CHANNEL)
|
||||
.setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"parseMagnetBasicInfo" -> {
|
||||
"addTorrent" -> {
|
||||
val magnetUri = call.argument<String>("magnetUri")
|
||||
if (magnetUri != null) parseMagnetBasicInfo(magnetUri, result)
|
||||
else result.error("INVALID_ARGUMENT", "magnetUri is required", null)
|
||||
val savePath = call.argument<String>("savePath")
|
||||
if (magnetUri != null && savePath != null) {
|
||||
addTorrent(magnetUri, savePath, result)
|
||||
} else {
|
||||
result.error("INVALID_ARGUMENT", "magnetUri and savePath are required", null)
|
||||
}
|
||||
}
|
||||
"fetchFullMetadata" -> {
|
||||
val magnetUri = call.argument<String>("magnetUri")
|
||||
if (magnetUri != null) fetchFullMetadata(magnetUri, result)
|
||||
else result.error("INVALID_ARGUMENT", "magnetUri is required", null)
|
||||
"getTorrents" -> getTorrents(result)
|
||||
"getTorrent" -> {
|
||||
val infoHash = call.argument<String>("infoHash")
|
||||
if (infoHash != null) getTorrent(infoHash, result)
|
||||
else result.error("INVALID_ARGUMENT", "infoHash is required", null)
|
||||
}
|
||||
"pauseTorrent" -> {
|
||||
val infoHash = call.argument<String>("infoHash")
|
||||
if (infoHash != null) pauseTorrent(infoHash, result)
|
||||
else result.error("INVALID_ARGUMENT", "infoHash is required", null)
|
||||
}
|
||||
"resumeTorrent" -> {
|
||||
val infoHash = call.argument<String>("infoHash")
|
||||
if (infoHash != null) resumeTorrent(infoHash, result)
|
||||
else result.error("INVALID_ARGUMENT", "infoHash is required", null)
|
||||
}
|
||||
"removeTorrent" -> {
|
||||
val infoHash = call.argument<String>("infoHash")
|
||||
val deleteFiles = call.argument<Boolean>("deleteFiles") ?: false
|
||||
if (infoHash != null) removeTorrent(infoHash, deleteFiles, result)
|
||||
else result.error("INVALID_ARGUMENT", "infoHash is required", null)
|
||||
}
|
||||
"setFilePriority" -> {
|
||||
val infoHash = call.argument<String>("infoHash")
|
||||
val fileIndex = call.argument<Int>("fileIndex")
|
||||
val priority = call.argument<Int>("priority")
|
||||
if (infoHash != null && fileIndex != null && priority != null) {
|
||||
setFilePriority(infoHash, fileIndex, priority, result)
|
||||
} else {
|
||||
result.error("INVALID_ARGUMENT", "infoHash, fileIndex, and priority are required", null)
|
||||
}
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseMagnetBasicInfo(magnetUri: String, result: MethodChannel.Result) {
|
||||
private fun addTorrent(magnetUri: String, savePath: String, result: MethodChannel.Result) {
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
val basicInfo = TorrentMetadataService.parseMagnetBasicInfo(magnetUri)
|
||||
if (basicInfo != null) {
|
||||
result.success(gson.toJson(basicInfo))
|
||||
} else {
|
||||
result.error("PARSE_ERROR", "Failed to parse magnet URI", null)
|
||||
val infoHash = withContext(Dispatchers.IO) {
|
||||
torrentEngine.addTorrent(magnetUri, savePath)
|
||||
}
|
||||
result.success(infoHash)
|
||||
} catch (e: Exception) {
|
||||
result.error("EXCEPTION", e.message, null)
|
||||
Log.e(TAG, "Failed to add torrent", e)
|
||||
result.error("ADD_TORRENT_ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchFullMetadata(magnetUri: String, result: MethodChannel.Result) {
|
||||
private fun getTorrents(result: MethodChannel.Result) {
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
val metadata = TorrentMetadataService.fetchFullMetadata(magnetUri)
|
||||
if (metadata != null) {
|
||||
TorrentDisplayUtils.logTorrentStructure(metadata)
|
||||
result.success(gson.toJson(metadata))
|
||||
val torrents = withContext(Dispatchers.IO) {
|
||||
torrentEngine.getAllTorrents()
|
||||
}
|
||||
val torrentsJson = torrents.map { torrent ->
|
||||
mapOf(
|
||||
"infoHash" to torrent.infoHash,
|
||||
"name" to torrent.name,
|
||||
"magnetUri" to torrent.magnetUri,
|
||||
"totalSize" to torrent.totalSize,
|
||||
"downloadedSize" to torrent.downloadedSize,
|
||||
"uploadedSize" to torrent.uploadedSize,
|
||||
"downloadSpeed" to torrent.downloadSpeed,
|
||||
"uploadSpeed" to torrent.uploadSpeed,
|
||||
"progress" to torrent.progress,
|
||||
"state" to torrent.state.name,
|
||||
"numPeers" to torrent.numPeers,
|
||||
"numSeeds" to torrent.numSeeds,
|
||||
"savePath" to torrent.savePath,
|
||||
"files" to torrent.files.map { file ->
|
||||
mapOf(
|
||||
"index" to file.index,
|
||||
"path" to file.path,
|
||||
"size" to file.size,
|
||||
"downloaded" to file.downloaded,
|
||||
"priority" to file.priority.value,
|
||||
"progress" to file.progress
|
||||
)
|
||||
},
|
||||
"addedDate" to torrent.addedDate,
|
||||
"error" to torrent.error
|
||||
)
|
||||
}
|
||||
result.success(gson.toJson(torrentsJson))
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get torrents", e)
|
||||
result.error("GET_TORRENTS_ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTorrent(infoHash: String, result: MethodChannel.Result) {
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
val torrent = withContext(Dispatchers.IO) {
|
||||
torrentEngine.getTorrent(infoHash)
|
||||
}
|
||||
if (torrent != null) {
|
||||
val torrentJson = mapOf(
|
||||
"infoHash" to torrent.infoHash,
|
||||
"name" to torrent.name,
|
||||
"magnetUri" to torrent.magnetUri,
|
||||
"totalSize" to torrent.totalSize,
|
||||
"downloadedSize" to torrent.downloadedSize,
|
||||
"uploadedSize" to torrent.uploadedSize,
|
||||
"downloadSpeed" to torrent.downloadSpeed,
|
||||
"uploadSpeed" to torrent.uploadSpeed,
|
||||
"progress" to torrent.progress,
|
||||
"state" to torrent.state.name,
|
||||
"numPeers" to torrent.numPeers,
|
||||
"numSeeds" to torrent.numSeeds,
|
||||
"savePath" to torrent.savePath,
|
||||
"files" to torrent.files.map { file ->
|
||||
mapOf(
|
||||
"index" to file.index,
|
||||
"path" to file.path,
|
||||
"size" to file.size,
|
||||
"downloaded" to file.downloaded,
|
||||
"priority" to file.priority.value,
|
||||
"progress" to file.progress
|
||||
)
|
||||
},
|
||||
"addedDate" to torrent.addedDate,
|
||||
"error" to torrent.error
|
||||
)
|
||||
result.success(gson.toJson(torrentJson))
|
||||
} else {
|
||||
result.error("METADATA_ERROR", "Failed to fetch torrent metadata", null)
|
||||
result.error("NOT_FOUND", "Torrent not found", null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
result.error("EXCEPTION", e.message, null)
|
||||
Log.e(TAG, "Failed to get torrent", e)
|
||||
result.error("GET_TORRENT_ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun pauseTorrent(infoHash: String, result: MethodChannel.Result) {
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
torrentEngine.pauseTorrent(infoHash)
|
||||
}
|
||||
result.success(true)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to pause torrent", e)
|
||||
result.error("PAUSE_TORRENT_ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resumeTorrent(infoHash: String, result: MethodChannel.Result) {
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
torrentEngine.resumeTorrent(infoHash)
|
||||
}
|
||||
result.success(true)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to resume torrent", e)
|
||||
result.error("RESUME_TORRENT_ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeTorrent(infoHash: String, deleteFiles: Boolean, result: MethodChannel.Result) {
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
torrentEngine.removeTorrent(infoHash, deleteFiles)
|
||||
}
|
||||
result.success(true)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to remove torrent", e)
|
||||
result.error("REMOVE_TORRENT_ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setFilePriority(infoHash: String, fileIndex: Int, priorityValue: Int, result: MethodChannel.Result) {
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
val priority = FilePriority.fromValue(priorityValue)
|
||||
withContext(Dispatchers.IO) {
|
||||
torrentEngine.setFilePriority(infoHash, fileIndex, priority)
|
||||
}
|
||||
result.success(true)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to set file priority", e)
|
||||
result.error("SET_PRIORITY_ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,6 +238,6 @@ class MainActivity : FlutterActivity() {
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
coroutineScope.cancel()
|
||||
TorrentMetadataService.cleanup()
|
||||
torrentEngine.shutdown()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user