diff --git a/CMakeLists.txt b/CMakeLists.txt index 06cd88b..5c09cfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.4.1) -project(transmissionbtc VERSION 1.0.0 LANGUAGES C) +project(transmissionbtc) if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type: [Debug|Release]" FORCE) @@ -8,7 +8,7 @@ endif () set(EXT_C_FLAGS "-DANDROID -fno-unwind-tables -no-canonical-prefixes -D_FORTIFY_SOURCE=1 \ -D_FILE_OFFSET_BITS=64 -D_LARGE_FILES=1") -set(EXT_CXX_FLAGS "${EXT_C_FLAGS} -fno-exceptions -fno-rtti") +set(EXT_CXX_FLAGS "${EXT_C_FLAGS} -fno-exceptions -frtti -std=gnu++17") set(EXT_EXE_LINKER_FLAGS "-Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libgcc_real.a \ -Wl,--exclude-libs,libatomic.a -Wl,--gc-sections -static-libstdc++") @@ -19,8 +19,10 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug") set(CMAKE_EXE_LINKER_FLAGS_DEBUG ${EXT_EXE_LINKER_FLAGS}) else () set(EXT_C_FLAGS "-O3 -DNDEBUG -flto -fvisibility=hidden -fdata-sections -ffunction-sections ${EXT_C_FLAGS}") - set(EXT_EXE_LINKER_FLAGS "-flto -O3 -Wl,--strip-all ${EXT_EXE_LINKER_FLAGS}") + set(EXT_CXX_FLAGS "-O3 -DNDEBUG -flto -fvisibility=hidden -fdata-sections -ffunction-sections ${EXT_CXX_FLAGS}") + set(EXT_EXE_LINKER_FLAGS "-fuse-ld=gold -flto -O3 -Wl,--strip-all ${EXT_EXE_LINKER_FLAGS}") set(CMAKE_C_FLAGS_RELEASE ${EXT_C_FLAGS}) + set(CMAKE_CXX_FLAGS_RELEASE ${EXT_CXX_FLAGS}) set(CMAKE_EXE_LINKER_FLAGS_RELEASE ${EXT_EXE_LINKER_FLAGS}) endif () @@ -41,15 +43,15 @@ set(EXT_CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} ) add_library(${PROJECT_NAME} SHARED - "${CMAKE_SOURCE_DIR}/src/main/cpp/native_to_java.c" - "${CMAKE_SOURCE_DIR}/src/main/cpp/env.c" - "${CMAKE_SOURCE_DIR}/src/main/cpp/sem.c" - "${CMAKE_SOURCE_DIR}/src/main/cpp/curl.c" - "${CMAKE_SOURCE_DIR}/src/main/cpp/hash.c" - "${CMAKE_SOURCE_DIR}/src/main/cpp/commons.c" - "${CMAKE_SOURCE_DIR}/src/main/cpp/torrent.c" - "${CMAKE_SOURCE_DIR}/src/main/cpp/transmission.c" - "${CMAKE_SOURCE_DIR}/src/main/cpp/stdredirect.c" + "${CMAKE_SOURCE_DIR}/src/main/cpp/native_to_java.cc" + "${CMAKE_SOURCE_DIR}/src/main/cpp/env.cc" + "${CMAKE_SOURCE_DIR}/src/main/cpp/sem.cc" + "${CMAKE_SOURCE_DIR}/src/main/cpp/curl.cc" + "${CMAKE_SOURCE_DIR}/src/main/cpp/hash.cc" + "${CMAKE_SOURCE_DIR}/src/main/cpp/commons.cc" + "${CMAKE_SOURCE_DIR}/src/main/cpp/torrent.cc" + "${CMAKE_SOURCE_DIR}/src/main/cpp/transmission.cc" + "${CMAKE_SOURCE_DIR}/src/main/cpp/stdredirect.cc" ) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) diff --git a/build.gradle b/build.gradle index d2c29f2..734a21e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ -def version = '1.3.8' -def versionNum = 9000017 +def version = '1.3.10' +def versionNum = 9000020 def webInstallDir = "${project.buildDir}/web" buildscript { diff --git a/cmake/Transmission.cmake b/cmake/Transmission.cmake index 54b6cb6..f03e6f8 100644 --- a/cmake/Transmission.cmake +++ b/cmake/Transmission.cmake @@ -11,6 +11,7 @@ set(TR_CMAKE_ARGS -DENABLE_TESTS=OFF -DENABLE_DAEMON=OFF -DINSTALL_DOC=OFF -DENA set(TR_LIBRARIES "${TR_BUILD_DIR}/libtransmission/libtransmission.a" "${TR_BUILD_DIR}/third-party/dht/lib/libdht.a" + "${TR_BUILD_DIR}/third-party/arc4/src/libarc4.a" "${TR_BUILD_DIR}/third-party/b64/lib/libb64.a" "${TR_BUILD_DIR}/third-party/natpmp/lib/libnatpmp.a" "${TR_BUILD_DIR}/third-party/miniupnpc/lib/libminiupnpc.a" diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 897cb97..5999e2c 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -9,7 +9,6 @@ - @@ -18,7 +17,7 @@ + tools:ignore="ermissions" /> -#include "transmission-private.h" - -jint throw(const char *file, int line, JNIEnv *env, const char *className, - const char *format, ...) { - int len; - char msg[256]; - char *fmt = (char *) format; - -#ifndef NDEBUG - char dfmt[1024]; - len = snprintf(dfmt, sizeof(dfmt), "[%s:%d] %s", file, line, format); - if (len > 0) fmt = dfmt; -#endif - - va_list args; - va_start(args, format); - len = vsnprintf(msg, sizeof(msg), (const char *) fmt, args); - va_end(args); - if (len > 0) fmt = msg; - - jclass c = (*env)->FindClass(env, className); - if (c == NULL) (*env)->FindClass(env, "java/lang/Error"); - - if ((*env)->ExceptionCheck(env)) { - logErr("Exception: %s", fmt); - } else { - (*env)->ThrowNew(env, c, fmt); - } - - return 0; -} - -size_t cp(JNIEnv *env, const char *fromPath, const char *toPath) { - FILE *from = NULL, *to = NULL; - size_t count = 0; - - if ((from = fopen(fromPath, "rb")) == NULL) { - throwIOEX(env, "Failed to open source file %s", fromPath); - } - - if ((to = fopen(toPath, "wb")) == NULL) { - fclose(from); - throwIOEX(env, "Failed to open destination file %s", toPath); - } - - size_t n; - char buffer[BUFSIZ]; - - while ((n = fread(buffer, sizeof(char), sizeof(buffer), from)) > 0) { - if (fwrite(buffer, sizeof(char), n, to) != n) { - throwIOEX(env, "Error writing to destination file %s", toPath); - } else { - count += n; - } - } - - CATCH: - if (from != NULL) fclose(from); - if (to != NULL) fclose(to); - return count; -} - -tr_ctor *ctorFromFile(JNIEnv *env, jlong jsession, jstring jpath, bool throwErr) { - const tr_session *session = (const tr_session *) jsession; - const char *path = (*env)->GetStringUTFChars(env, jpath, 0); - tr_ctor *ctor = tr_ctorNew(session); - - if (tr_ctorSetMetainfoFromFile(ctor, path)) { - tr_ctorFree(ctor); - ctor = NULL; - throwOrLog(env, CLASS_IOEX, throwErr, "Invalid torrent file: %s", path); - } - - CATCH: - (*env)->ReleaseStringUTFChars(env, jpath, path); - return ctor; -} - -tr_parse_result -infoFromFile(JNIEnv *env, jlong jsession, jstring jpath, tr_info *info, bool throwErr) { - const char *path = NULL; - tr_parse_result result = TR_PARSE_ERR; - tr_ctor *ctor = ctorFromFileEx(env, jsession, jpath); - result = tr_torrentParse(ctor, info); - tr_ctorFree(ctor); - - if (result != TR_PARSE_OK) { - path = (*env)->GetStringUTFChars(env, jpath, 0); - throwOrLog(env, CLASS_IOEX, throwErr, "Failed to parse torrent file: %s", path); - } - - CATCH: - if (path != NULL) (*env)->ReleaseStringUTFChars(env, jpath, path); - return result; -} - -int findTorrentByHash(tr_session *session, uint8_t *hash, Err *err) { - tr_torrent *tor = tr_torrentFindFromHash(session, hash); - - if (tor == NULL) { - char hashString[1 + 2 * SHA_DIGEST_LENGTH]; - tr_binary_to_hex(hash, hashString, SHA_DIGEST_LENGTH); - err->set(err, CLASS_NSTEX, "No such torrent: hash=%s", hashString); - return -1; - } - - return tr_torrentId(tor); -} - -tr_torrent *findTorrentById(tr_session *session, int id, Err *err) { - tr_torrent *tor = tr_torrentFindFromId(session, id); - if (tor == NULL) err->set(err, CLASS_NSTEX, "No such torrent: id=%d", id); - return tor; -} - -const tr_file *getFileInfo(tr_torrent *tor, uint32_t idx, Err *err) { - const tr_info *info = tr_torrentInfo(tor); - if (idx >= info->fileCount) { - err->set(err, CLASS_IAEX, "Invalid file index: %d", idx); - return NULL; - } else { - return &info->files[idx]; - } -} - -const tr_file *getWantedFileInfo(tr_torrent *tor, uint32_t idx, Err *err) { - const tr_file *f = getFileInfoEx(tor, idx, err); - if (f->dnd != 0) { - err->set(err, CLASS_IAEX, "File #%d is unwanted for download: %s", idx, f->name); - return NULL; - } - CATCH: - return f; -} - -struct Future { - Err err; - sem_t sem; - tr_session *session; - void *userData; - void *result; - const char *ex; - char *exMsg; - uint16_t exMsgBufLen; - - void *(*func)(tr_session *session, void *userData, Err *err); -}; - -static void setError(struct Err *err, const char *ex, const char *msg, ...) { - struct Future *f = (struct Future *) err; - - if (f->exMsg == NULL) { - f->exMsgBufLen = 512; - f->exMsg = calloc(f->exMsgBufLen, sizeof(char)); - } - - va_list args; - va_start(args, msg); - vsnprintf(f->exMsg, f->exMsgBufLen, msg, args); - va_end(args); - f->ex = ex; - f->err.isSet = true; -} - -static void runInEventThread(void *data) { - struct Future *f = (struct Future *) data; - f->result = f->func(f->session, f->userData, &(f->err)); - sem_post(&(f->sem)); -} - -void *runInTransmissionThread(const char *file, int line, JNIEnv *env, jlong jsession, - void *(*func)(tr_session *, void *, Err *), - void *userData) { - struct Future f; - sem_t *sem = &(f.sem); - memset(sem, 0, sizeof(sem_t)); - sem_init(sem, 0, 0); - f.session = (tr_session *) jsession; - f.userData = userData; - f.ex = NULL; - f.exMsg = NULL; - f.err.isSet = false; - f.err.set = setError; - f.func = func; - - tr_runInEventThread(f.session, runInEventThread, &f); - while ((sem_wait(sem) == -1) && (errno == EINTR)); - sem_destroy(sem); - - if (f.err.isSet) { - throw(file, line, env, f.ex, f.exMsg); - free(f.exMsg); - return NULL; - } else { - return f.result; - } -} \ No newline at end of file diff --git a/src/main/cpp/commons.cc b/src/main/cpp/commons.cc new file mode 100644 index 0000000..5e9d58b --- /dev/null +++ b/src/main/cpp/commons.cc @@ -0,0 +1,202 @@ +#include "commons.h" +#include +#include "transmission-private.h" + +extern "C" { + +jint throwException(const char *file, int line, JNIEnv *env, const char *className, + const char *format, ...) { + int len; + char msg[256]; + char *fmt = (char *) format; + +#ifndef NDEBUG + char dfmt[1024]; + len = snprintf(dfmt, sizeof(dfmt), "[%s:%d] %s", file, line, format); + if (len > 0) fmt = dfmt; +#endif + + va_list args; + va_start(args, format); + len = vsnprintf(msg, sizeof(msg), (const char *) fmt, args); + va_end(args); + if (len > 0) fmt = msg; + + jclass c = env->FindClass(className); + if (c == NULL) env->FindClass("java/lang/Error"); + + if (env->ExceptionCheck()) { + logErr("Exception: %s", fmt); + } else { + env->ThrowNew(c, fmt); + } + + return 0; +} + +size_t cp(JNIEnv *env, const char *fromPath, const char *toPath) { + FILE *from = NULL, *to = NULL; + size_t count = 0; + + if ((from = fopen(fromPath, "rb")) == NULL) { + throwIOEX(env, "Failed to open source file %s", fromPath); + } + + if ((to = fopen(toPath, "wb")) == NULL) { + fclose(from); + throwIOEX(env, "Failed to open destination file %s", toPath); + } + + size_t n; + char buffer[BUFSIZ]; + + while ((n = fread(buffer, sizeof(char), sizeof(buffer), from)) > 0) { + if (fwrite(buffer, sizeof(char), n, to) != n) { + throwIOEX(env, "Error writing to destination file %s", toPath); + } else { + count += n; + } + } + + CATCH: + if (from != NULL) fclose(from); + if (to != NULL) fclose(to); + return count; +} + +tr_ctor *ctorFromFile(JNIEnv *env, jlong jsession, jstring jpath, bool throwErr) { + const tr_session *session = (const tr_session *) jsession; + const char *path = env->GetStringUTFChars(jpath, 0); + tr_ctor *ctor = tr_ctorNew(session); + + if (tr_ctorSetMetainfoFromFile(ctor, path)) { + tr_ctorFree(ctor); + ctor = NULL; + throwOrLog(env, CLASS_IOEX, throwErr, "Invalid torrent file: %s", path); + } + + CATCH: + env->ReleaseStringUTFChars(jpath, path); + return ctor; +} + +tr_parse_result +infoFromFile(JNIEnv *env, jlong jsession, jstring jpath, tr_info *info, bool throwErr) { + const char *path = NULL; + tr_parse_result result = TR_PARSE_ERR; + tr_ctor *ctor = ctorFromFileEx(env, jsession, jpath); + result = tr_torrentParse(ctor, info); + tr_ctorFree(ctor); + + if (result != TR_PARSE_OK) { + path = env->GetStringUTFChars(jpath, 0); + throwOrLog(env, CLASS_IOEX, throwErr, "Failed to parse torrent file: %s", path); + } + + CATCH: + if (path != NULL) env->ReleaseStringUTFChars(jpath, path); + return result; +} + +int findTorrentByHash(tr_session *session, uint8_t *hash, Err *err) { + tr_torrent *tor = tr_torrentFindFromHash(session, hash); + + if (tor == NULL) { + char hashString[1 + 2 * SHA_DIGEST_LENGTH]; + tr_binary_to_hex(hash, hashString, SHA_DIGEST_LENGTH); + err->set(err, CLASS_NSTEX, "No such torrent: hash=%s", hashString); + return -1; + } + + return tr_torrentId(tor); +} + +tr_torrent *findTorrentById(tr_session *session, int id, Err *err) { + tr_torrent *tor = tr_torrentFindFromId(session, id); + if (tor == NULL) err->set(err, CLASS_NSTEX, "No such torrent: id=%d", id); + return tor; +} + +const tr_file *getFileInfo(tr_torrent *tor, uint32_t idx, Err *err) { + const tr_info *info = tr_torrentInfo(tor); + if (idx >= info->fileCount) { + err->set(err, CLASS_IAEX, "Invalid file index: %d", idx); + return NULL; + } else { + return &info->files[idx]; + } +} + +const tr_file *getWantedFileInfo(tr_torrent *tor, uint32_t idx, Err *err) { + const tr_file *f = getFileInfoEx(tor, idx, err); + if (f->dnd != 0) { + err->set(err, CLASS_IAEX, "File #%d is unwanted for download: %s", idx, f->name); + return NULL; + } + CATCH: + return f; +} + +struct Future { + Err err; + sem_t sem; + tr_session *session; + void *userData; + void *result; + const char *ex; + char *exMsg; + uint16_t exMsgBufLen; + + void *(*func)(tr_session *session, void *userData, Err *err); +}; + +static void setError(struct Err *err, const char *ex, const char *msg, ...) { + struct Future *f = (struct Future *) err; + + if (f->exMsg == NULL) { + f->exMsgBufLen = 512; + f->exMsg = (char *) calloc(f->exMsgBufLen, sizeof(char)); + } + + va_list args; + va_start(args, msg); + vsnprintf(f->exMsg, f->exMsgBufLen, msg, args); + va_end(args); + f->ex = ex; + f->err.isSet = true; +} + +static void runInEventThread(void *data) { + struct Future *f = (struct Future *) data; + f->result = f->func(f->session, f->userData, &(f->err)); + sem_post(&(f->sem)); +} + +void *runInTransmissionThread(const char *file, int line, JNIEnv *env, jlong jsession, + void *(*func)(tr_session *, void *, Err *), + void *userData) { + struct Future f; + sem_t *sem = &(f.sem); + memset(sem, 0, sizeof(sem_t)); + sem_init(sem, 0, 0); + f.session = (tr_session *) jsession; + f.userData = userData; + f.ex = NULL; + f.exMsg = NULL; + f.err.isSet = false; + f.err.set = setError; + f.func = func; + + tr_runInEventThread(f.session, runInEventThread, &f); + while ((sem_wait(sem) == -1) && (errno == EINTR)); + sem_destroy(sem); + + if (f.err.isSet) { + throwException(file, line, env, f.ex, f.exMsg); + free(f.exMsg); + return NULL; + } else { + return f.result; + } +} +} // extern "C" \ No newline at end of file diff --git a/src/main/cpp/commons.h b/src/main/cpp/commons.h index 73a22a5..2c8cca2 100644 --- a/src/main/cpp/commons.h +++ b/src/main/cpp/commons.h @@ -11,6 +11,8 @@ #include #include +extern "C" { + #define LOG_TAG "transmissionbtc" #define CLASS_IOEX "java/io/IOException" #define CLASS_IAEX "java/lang/IllegalArgumentException" @@ -21,7 +23,7 @@ #define logErr(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) -#define throwEX(env, className, ...) { throw(__FILENAME__, __LINE__, env, className, __VA_ARGS__); goto CATCH; } +#define throwEX(env, className, ...) { throwException(__FILENAME__, __LINE__, env, className, __VA_ARGS__); goto CATCH; } #define throwIOEX(env, ...) throwEX(env, CLASS_IOEX, __VA_ARGS__) #define throwOrLog(env, className, throw, ...) \ if (throw) { throwEX(env, className, __VA_ARGS__); } \ @@ -34,18 +36,18 @@ typedef struct Err { } Err; #define errCheck(err) if (err->isSet) goto CATCH -jint throw(const char *, int, JNIEnv *, const char *, const char *, ...); +jint throwException(const char *, int, JNIEnv *, const char *, const char *, ...); size_t cp(JNIEnv *env, const char *, const char *); #define ctorFromFileEx(env, jsession, jpath) \ ctorFromFile(env, jsession, jpath, true); \ - if ((*env)->ExceptionCheck(env)) goto CATCH + if (env->ExceptionCheck()) goto CATCH tr_ctor *ctorFromFile(JNIEnv *, jlong, jstring, bool); #define infoFromFileEx(env, jsession, jpath, info) \ - if ((infoFromFile(env, jsession, jpath, info, true) != TR_PARSE_OK) || (*env)->ExceptionCheck(env)) \ + if ((infoFromFile(env, jsession, jpath, info, true) != TR_PARSE_OK) || env->ExceptionCheck()) \ goto CATCH tr_parse_result infoFromFile(JNIEnv *, jlong, jstring, tr_info *, bool); @@ -68,10 +70,11 @@ const tr_file *getWantedFileInfo(tr_torrent *tor, uint32_t idx, Err *err); #define runInTransmissionThreadEx(env, jsession, func, data) \ runInTransmissionThread(__FILENAME__, __LINE__, env, jsession, func, data);\ - if ((*env)->ExceptionCheck(env)) goto CATCH + if (env->ExceptionCheck()) goto CATCH void *runInTransmissionThread(const char *file, int line, JNIEnv *env, jlong jsession, void *(*func)(tr_session *session, void *userData, Err *err), void *userData); +} // extern "C" #endif //TRANSMISSIONBTC_COMMONS_H diff --git a/src/main/cpp/curl.c b/src/main/cpp/curl.cc similarity index 86% rename from src/main/cpp/curl.c rename to src/main/cpp/curl.cc index 29fb911..5147c57 100644 --- a/src/main/cpp/curl.c +++ b/src/main/cpp/curl.cc @@ -5,12 +5,14 @@ #include #include +extern "C" { JNIEXPORT void JNICALL Java_com_ap_transmission_btc_Native_curl(JNIEnv *env, jclass __unused c, jstring jurl, jstring jdst, jint timeout) { - const char *dst = (*env)->GetStringUTFChars(env, jdst, 0); + const char *dst = env->GetStringUTFChars(jdst, 0); FILE *file = fopen(dst, "wb"); +{ if (file == NULL) { throwEX(env, CLASS_IOEX, "Failed to open file %s: %s", dst, strerror(errno)); } @@ -19,7 +21,7 @@ Java_com_ap_transmission_btc_Native_curl(JNIEnv *env, jclass __unused c, if (curl) { char err[CURL_ERROR_SIZE]; - const char *url = (*env)->GetStringUTFChars(env, jurl, 0); + const char *url = env->GetStringUTFChars(jurl, 0); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL); @@ -36,7 +38,7 @@ Java_com_ap_transmission_btc_Native_curl(JNIEnv *env, jclass __unused c, #endif CURLcode res = curl_easy_perform(curl); - (*env)->ReleaseStringUTFChars(env, jurl, url); + env->ReleaseStringUTFChars(jurl, url); curl_easy_cleanup(curl); if (res != CURLE_OK) { @@ -46,7 +48,8 @@ Java_com_ap_transmission_btc_Native_curl(JNIEnv *env, jclass __unused c, throwEX(env, CLASS_IOEX, "Failed to initialize curl"); } - CATCH: - (*env)->ReleaseStringUTFChars(env, jdst, dst); +} CATCH: + env->ReleaseStringUTFChars(jdst, dst); if (file != NULL) fclose(file); } +} //extern "C" \ No newline at end of file diff --git a/src/main/cpp/env.c b/src/main/cpp/env.c deleted file mode 100644 index 1bffa93..0000000 --- a/src/main/cpp/env.c +++ /dev/null @@ -1,24 +0,0 @@ -#include "commons.h" -#include - -JNIEXPORT void JNICALL -Java_com_ap_transmission_btc_Native_envUnset( - JNIEnv *env, jclass __unused c, jstring jname) { - const char *name = (*env)->GetStringUTFChars(env, jname, 0); - unsetenv(name); - (*env)->ReleaseStringUTFChars(env, jname, name); -} - -JNIEXPORT void JNICALL -Java_com_ap_transmission_btc_Native_envSet( - JNIEnv *env, jclass __unused c, jstring jname, jstring jvalue) { - if ((jvalue == NULL) || ((*env)->GetStringUTFLength(env, jvalue) == 0)) { - Java_com_ap_transmission_btc_Native_envUnset(env, c, jname); - } else { - const char *name = (*env)->GetStringUTFChars(env, jname, 0); - const char *value = (*env)->GetStringUTFChars(env, jvalue, 0); - setenv(name, value, 1); - (*env)->ReleaseStringUTFChars(env, jname, name); - (*env)->ReleaseStringUTFChars(env, jvalue, value); - } -} \ No newline at end of file diff --git a/src/main/cpp/env.cc b/src/main/cpp/env.cc new file mode 100644 index 0000000..4cadf73 --- /dev/null +++ b/src/main/cpp/env.cc @@ -0,0 +1,26 @@ +#include "commons.h" +#include + +extern "C" { +JNIEXPORT void JNICALL +Java_com_ap_transmission_btc_Native_envUnset( + JNIEnv *env, jclass __unused c, jstring jname) { + const char *name = env->GetStringUTFChars(jname, 0); + unsetenv(name); + env->ReleaseStringUTFChars(jname, name); +} + +JNIEXPORT void JNICALL +Java_com_ap_transmission_btc_Native_envSet( + JNIEnv *env, jclass __unused c, jstring jname, jstring jvalue) { + if ((jvalue == NULL) || (env->GetStringUTFLength(jvalue) == 0)) { + Java_com_ap_transmission_btc_Native_envUnset(env, c, jname); + } else { + const char *name = env->GetStringUTFChars(jname, 0); + const char *value = env->GetStringUTFChars(jvalue, 0); + setenv(name, value, 1); + env->ReleaseStringUTFChars(jname, name); + env->ReleaseStringUTFChars(jvalue, value); + } +} +} // extern "C" \ No newline at end of file diff --git a/src/main/cpp/hash.c b/src/main/cpp/hash.cc similarity index 56% rename from src/main/cpp/hash.c rename to src/main/cpp/hash.cc index 183577a..7466600 100644 --- a/src/main/cpp/hash.c +++ b/src/main/cpp/hash.cc @@ -2,6 +2,7 @@ #include #include +extern "C" { JNIEXPORT jint JNICALL Java_com_ap_transmission_btc_Native_hashLength(JNIEnv *__unused env, jclass __unused c) { return SHA_DIGEST_LENGTH; @@ -12,32 +13,34 @@ Java_com_ap_transmission_btc_Native_hashBytesToString( JNIEnv *env, jclass __unused c, jbyteArray jhash) { char hash[SHA_DIGEST_LENGTH]; char hashString[1 + 2 * SHA_DIGEST_LENGTH]; - (*env)->GetByteArrayRegion(env, jhash, 0, sizeof(hash), (jbyte *) hash); + env->GetByteArrayRegion(jhash, 0, sizeof(hash), (jbyte *) hash); tr_binary_to_hex(hash, hashString, sizeof(hash)); - return (*env)->NewStringUTF(env, (const char *) hashString); + return env->NewStringUTF((const char *) hashString); } JNIEXPORT jbyteArray JNICALL Java_com_ap_transmission_btc_Native_hashStringToBytes( JNIEnv *env, jclass __unused c, jstring jhashString) { char hash[SHA_DIGEST_LENGTH]; - const char *hashString = (*env)->GetStringUTFChars(env, jhashString, 0); + const char *hashString = env->GetStringUTFChars(jhashString, 0); tr_hex_to_binary(hashString, hash, sizeof(hash)); - (*env)->ReleaseStringUTFChars(env, jhashString, hashString); - jbyteArray jhash = (*env)->NewByteArray(env, sizeof(hash)); - (*env)->SetByteArrayRegion(env, jhash, 0, sizeof(hash), (const jbyte *) hash); + env->ReleaseStringUTFChars(jhashString, hashString); + jbyteArray jhash = env->NewByteArray(sizeof(hash)); + env->SetByteArrayRegion(jhash, 0, sizeof(hash), (const jbyte *) hash); return jhash; } JNIEXPORT jbyteArray JNICALL Java_com_ap_transmission_btc_Native_hashGetTorrentHash( JNIEnv *env, jclass __unused c, jstring torrentPath) { - tr_info info; - infoFromFileEx(env, 0, torrentPath, &info); - jbyteArray jhash = (*env)->NewByteArray(env, SHA_DIGEST_LENGTH); - (*env)->SetByteArrayRegion(env, jhash, 0, SHA_DIGEST_LENGTH, (const jbyte *) info.hash); - tr_metainfoFree(&info); - return jhash; - CATCH: - return NULL; +{ + tr_info info; + infoFromFileEx(env, 0, torrentPath, &info); + jbyteArray jhash = env->NewByteArray(SHA_DIGEST_LENGTH); + env->SetByteArrayRegion(jhash, 0, SHA_DIGEST_LENGTH, (const jbyte *) info.hash); + tr_metainfoFree(&info); + return jhash; +} CATCH: + return NULL; } +} // extern "C" \ No newline at end of file diff --git a/src/main/cpp/native_to_java.c b/src/main/cpp/native_to_java.cc similarity index 56% rename from src/main/cpp/native_to_java.c rename to src/main/cpp/native_to_java.cc index 4dff322..0600d72 100644 --- a/src/main/cpp/native_to_java.c +++ b/src/main/cpp/native_to_java.cc @@ -7,6 +7,9 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" #pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection" + +extern "C" { + static JavaVM *jvm; static jclass classNative; static jclass classStorageAccess; @@ -21,86 +24,91 @@ static jmethodID renamePath; static jmethodID removePath; #define JvmAttach() \ - JNIEnv *env;\ - jint _detached = (*jvm)->GetEnv(jvm, &env, JNI_VERSION_1_2);\ - if ((_detached == JNI_EDETACHED) && ((*jvm)->AttachCurrentThread(jvm, &env, NULL) != JNI_OK)) {\ + void *__env__ = nullptr;\ + jint _detached = jvm->GetEnv(&__env__, JNI_VERSION_1_2);\ + JNIEnv *env = (JNIEnv*) __env__;\ + if ((_detached == JNI_EDETACHED) && (jvm->AttachCurrentThread(&env, NULL) != JNI_OK)) {\ logErr("JavaVM->AttachCurrentThread() failed");\ goto CATCH;\ } #define JvmDetach() \ - if ((*env)->ExceptionCheck(env)) logErr("Native to Java call failed");\ - if (_detached == JNI_EDETACHED) (*jvm)->DetachCurrentThread(jvm) + if (env->ExceptionCheck()) logErr("Native to Java call failed");\ + if (_detached == JNI_EDETACHED) jvm->DetachCurrentThread() static void rndChars(char *str, int num); JNIEXPORT void JNICALL Java_com_ap_transmission_btc_Native_nativeToJavaInit(JNIEnv *env, jclass c) { - (*env)->GetJavaVM(env, &jvm); - jclass sa = (*env)->FindClass(env, "com/ap/transmission/btc/StorageAccess"); - classNative = (jclass) (*env)->NewGlobalRef(env, c); - classStorageAccess = (jclass) (*env)->NewGlobalRef(env, sa); - addedOrChangedCallback = (*env)->GetStaticMethodID(env, c, "torrentAddedOrChangedCallback", + env->GetJavaVM(&jvm); + jclass sa = env->FindClass("com/ap/transmission/btc/StorageAccess"); + classNative = (jclass) env->NewGlobalRef(c); + classStorageAccess = (jclass) env->NewGlobalRef(sa); + addedOrChangedCallback = env->GetStaticMethodID(c, "torrentAddedOrChangedCallback", "()V"); - stoppedCallback = (*env)->GetStaticMethodID(env, c, "torrentStoppedCallback", "()V"); - sessionChangedCallback = (*env)->GetStaticMethodID(env, c, "sessionChangedCallback", "()V"); - scheduledAltSpeedCallback = (*env)->GetStaticMethodID(env, c, "scheduledAltSpeedCallback", "()V"); - createDir = (*env)->GetStaticMethodID(env, sa, "createDir", "(Ljava/lang/String;)Z"); - openFile = (*env)->GetStaticMethodID(env, sa, "openFile", "(Ljava/lang/String;ZZZ)I"); - closeFileDescriptor = (*env)->GetStaticMethodID(env, sa, "closeFileDescriptor", "(I)Z"); - renamePath = (*env)->GetStaticMethodID(env, sa, "renamePath", + stoppedCallback = env->GetStaticMethodID(c, "torrentStoppedCallback", "()V"); + sessionChangedCallback = env->GetStaticMethodID(c, "sessionChangedCallback", "()V"); + scheduledAltSpeedCallback = env->GetStaticMethodID(c, "scheduledAltSpeedCallback", "()V"); + createDir = env->GetStaticMethodID(sa, "createDir", "(Ljava/lang/String;)Z"); + openFile = env->GetStaticMethodID(sa, "openFile", "(Ljava/lang/String;ZZZ)I"); + closeFileDescriptor = env->GetStaticMethodID(sa, "closeFileDescriptor", "(I)Z"); + renamePath = env->GetStaticMethodID(sa, "renamePath", "(Ljava/lang/String;Ljava/lang/String;)Z"); - removePath = (*env)->GetStaticMethodID(env, sa, "removePath", "(Ljava/lang/String;)Z"); + removePath = env->GetStaticMethodID(sa, "removePath", "(Ljava/lang/String;)Z"); } void callAddedOrChangedCallback() { JvmAttach(); - (*env)->CallStaticVoidMethod(env, classNative, addedOrChangedCallback); + env->CallStaticVoidMethod(classNative, addedOrChangedCallback); JvmDetach(); CATCH:; } void callStoppedCallback() { JvmAttach(); - (*env)->CallStaticVoidMethod(env, classNative, stoppedCallback); + env->CallStaticVoidMethod(classNative, stoppedCallback); JvmDetach(); CATCH:; } void callSessionChangedCallback() { JvmAttach(); - (*env)->CallStaticVoidMethod(env, classNative, sessionChangedCallback); + env->CallStaticVoidMethod(classNative, sessionChangedCallback); JvmDetach(); CATCH:; } void callScheduledAltSpeedCallback() { JvmAttach(); - (*env)->CallStaticVoidMethod(env, classNative, scheduledAltSpeedCallback); + env->CallStaticVoidMethod(classNative, scheduledAltSpeedCallback); JvmDetach(); CATCH:; } +} // extern "C" + bool tr_android_dir_create(char const *path) { jboolean result = JNI_FALSE; +{ JvmAttach(); - jstring jpath = (*env)->NewStringUTF(env, path); - result = (*env)->CallStaticBooleanMethod(env, classStorageAccess, createDir, jpath); + jstring jpath = env->NewStringUTF(path); + result = env->CallStaticBooleanMethod(classStorageAccess, createDir, jpath); JvmDetach(); - CATCH: +} CATCH: return result == JNI_TRUE; } tr_sys_file_t tr_android_file_open(char const *path, int flags) { int fd = -1; +{ JvmAttach(); jboolean create = (flags & (TR_SYS_FILE_CREATE | TR_SYS_FILE_CREATE_NEW)) ? JNI_TRUE : JNI_FALSE; jboolean writable = JNI_TRUE; jboolean truncate = (flags & TR_SYS_FILE_TRUNCATE) ? JNI_TRUE : JNI_FALSE; - jstring jpath = (*env)->NewStringUTF(env, path); - fd = (*env)->CallStaticIntMethod(env, classStorageAccess, openFile, jpath, + jstring jpath = env->NewStringUTF(path); + fd = env->CallStaticIntMethod(classStorageAccess, openFile, jpath, create, writable, truncate); JvmDetach(); - CATCH: +} CATCH: return (fd == -1) ? TR_BAD_SYS_FILE : fd; } @@ -122,32 +130,35 @@ tr_sys_file_t tr_android_file_open_temp(char *path_template) { bool tr_android_file_close(tr_sys_file_t handle) { jboolean result = JNI_FALSE; +{ JvmAttach(); - result = (*env)->CallStaticBooleanMethod(env, classStorageAccess, closeFileDescriptor, + result = env->CallStaticBooleanMethod(classStorageAccess, closeFileDescriptor, (jint) handle); JvmDetach(); - CATCH: +} CATCH: return result == JNI_TRUE; } bool tr_android_path_rename(char const *src_path, char const *dst_path) { jboolean result = JNI_FALSE; +{ JvmAttach(); - jstring src = (*env)->NewStringUTF(env, src_path); - jstring dst = (*env)->NewStringUTF(env, dst_path); - result = (*env)->CallStaticBooleanMethod(env, classStorageAccess, renamePath, src, dst); + jstring src = env->NewStringUTF(src_path); + jstring dst = env->NewStringUTF(dst_path); + result = env->CallStaticBooleanMethod(classStorageAccess, renamePath, src, dst); JvmDetach(); - CATCH: +} CATCH: return result == JNI_TRUE; } bool tr_android_path_remove(char const *path) { jboolean result = JNI_FALSE; +{ JvmAttach(); - jstring jpath = (*env)->NewStringUTF(env, path); - result = (*env)->CallStaticBooleanMethod(env, classStorageAccess, removePath, jpath); + jstring jpath = env->NewStringUTF(path); + result = env->CallStaticBooleanMethod(classStorageAccess, removePath, jpath); JvmDetach(); - CATCH: +} CATCH: return result == JNI_TRUE; } diff --git a/src/main/cpp/native_to_java.h b/src/main/cpp/native_to_java.h index 85d8b83..6e722c9 100644 --- a/src/main/cpp/native_to_java.h +++ b/src/main/cpp/native_to_java.h @@ -5,6 +5,7 @@ #ifndef TRANSMISSIONBTC_NATIVE_TO_JAVA_H #define TRANSMISSIONBTC_NATIVE_TO_JAVA_H +extern "C" { void callAddedOrChangedCallback(); void callStoppedCallback(); @@ -13,4 +14,5 @@ void callSessionChangedCallback(); void callScheduledAltSpeedCallback(); +} // extern "C" #endif //TRANSMISSIONBTC_NATIVE_TO_JAVA_H diff --git a/src/main/cpp/sem.c b/src/main/cpp/sem.c deleted file mode 100644 index 02e4b72..0000000 --- a/src/main/cpp/sem.c +++ /dev/null @@ -1,26 +0,0 @@ -#include "commons.h" -#include -#include - -JNIEXPORT jlong JNICALL -Java_com_ap_transmission_btc_Native_semCreate(JNIEnv *__unused env, jclass __unused c) { - sem_t *sem = malloc(sizeof(sem_t)); - memset(sem, 0, sizeof(sem_t)); - sem_init(sem, 0, 0); - return (jlong) sem; -} - -JNIEXPORT void JNICALL -Java_com_ap_transmission_btc_Native_semDestroy(JNIEnv *__unused env, jclass __unused c, - jlong jsem) { - sem_t *sem = (sem_t *) jsem; - sem_destroy(sem); - free(sem); -} - -JNIEXPORT void JNICALL -Java_com_ap_transmission_btc_Native_semPost(JNIEnv *__unused env, jclass __unused c, - jlong jsem) { - sem_t *sem = (sem_t *) jsem; - sem_post(sem); -} \ No newline at end of file diff --git a/src/main/cpp/sem.cc b/src/main/cpp/sem.cc new file mode 100644 index 0000000..d3fba35 --- /dev/null +++ b/src/main/cpp/sem.cc @@ -0,0 +1,38 @@ +#include "commons.h" +#include +#include + +extern "C" { + +JNIEXPORT jlong +JNICALL +Java_com_ap_transmission_btc_Native_semCreate(JNIEnv *__unused env, jclass __unused c) { + sem_t *sem = (sem_t *) malloc(sizeof(sem_t)); + memset(sem, 0, sizeof(sem_t)); + sem_init(sem, 0, 0); + return (jlong) + sem; +} + +JNIEXPORT void JNICALL +Java_com_ap_transmission_btc_Native_semDestroy(JNIEnv * __unused env, jclass +__unused c, + jlong +jsem ) { +sem_t *sem = (sem_t *) jsem; +sem_destroy(sem); +free(sem); +} + +JNIEXPORT void JNICALL +Java_com_ap_transmission_btc_Native_semPost(JNIEnv +* +__unused env, jclass +__unused c, + jlong +jsem) { +sem_t *sem = (sem_t *) jsem; +sem_post(sem); +} + +} // extern "C" \ No newline at end of file diff --git a/src/main/cpp/stdredirect.c b/src/main/cpp/stdredirect.cc similarity index 97% rename from src/main/cpp/stdredirect.c rename to src/main/cpp/stdredirect.cc index a314ae6..4a1dc14 100644 --- a/src/main/cpp/stdredirect.c +++ b/src/main/cpp/stdredirect.cc @@ -11,6 +11,8 @@ Java_com_ap_transmission_btc_Native_stdRedirect(JNIEnv *__unused env, jclass __u #include #include +extern "C" { + static int pfd[2]; static pthread_t thr; @@ -43,4 +45,5 @@ Java_com_ap_transmission_btc_Native_stdRedirect(JNIEnv *env, jclass __unused c) CATCH:; } +} // extern "C" #endif \ No newline at end of file diff --git a/src/main/cpp/torrent.c b/src/main/cpp/torrent.cc similarity index 82% rename from src/main/cpp/torrent.c rename to src/main/cpp/torrent.cc index c663704..f35f0f1 100644 --- a/src/main/cpp/torrent.c +++ b/src/main/cpp/torrent.cc @@ -2,6 +2,7 @@ #include "transmission-private.h" #include +extern "C" { // ----------------------------------- torrentAdd -------------------------------------------------- /* * Returns: @@ -15,26 +16,26 @@ Java_com_ap_transmission_btc_Native_torrentAdd( JNIEnv *env, jclass __unused c, jlong jsession, jstring jpath, jstring jdownloadDir, jboolean setDelete, jboolean sequential, jintArray unwantedIndexes, jbyteArray returnMeTorrentHash) { - bool delete = false; + bool del = false; tr_ctor *ctor = ctorFromFile(env, jsession, jpath, false); if (ctor != NULL) { if (jdownloadDir != NULL) { - const char *downloadDir = (*env)->GetStringUTFChars(env, jdownloadDir, 0); + const char *downloadDir = env->GetStringUTFChars(jdownloadDir, 0); tr_ctorSetDownloadDir(ctor, TR_FORCE, downloadDir); - (*env)->ReleaseStringUTFChars(env, jdownloadDir, downloadDir); + env->ReleaseStringUTFChars(jdownloadDir, downloadDir); } if (unwantedIndexes != NULL) { - jsize len = (*env)->GetArrayLength(env, unwantedIndexes); - jint *unwanted = (*env)->GetIntArrayElements(env, unwantedIndexes, 0); + jsize len = env->GetArrayLength(unwantedIndexes); + jint *unwanted = env->GetIntArrayElements(unwantedIndexes, 0); tr_file_index_t trIndexes[len]; for (int i = 0; i < len; i++) { trIndexes[i] = (tr_file_index_t) unwanted[i]; } - (*env)->ReleaseIntArrayElements(env, unwantedIndexes, unwanted, 0); + env->ReleaseIntArrayElements(unwantedIndexes, unwanted, 0); tr_ctorSetFilesWanted(ctor, trIndexes, (tr_file_index_t) len, false); } @@ -54,8 +55,8 @@ Java_com_ap_transmission_btc_Native_torrentAdd( const tr_info *info = tr_torrentInfo(tor); tr_file_index_t wanted[info->fileCount]; tr_file_index_t wantedCount = 0; - jsize len = (*env)->GetArrayLength(env, unwantedIndexes); - jint *unwanted = (*env)->GetIntArrayElements(env, unwantedIndexes, 0); + jsize len = env->GetArrayLength(unwantedIndexes); + jint *unwanted = env->GetIntArrayElements(unwantedIndexes, 0); for (int i = 0; i < info->fileCount; i++) { tr_file f = info->files[i]; @@ -72,23 +73,23 @@ Java_com_ap_transmission_btc_Native_torrentAdd( if (w) wanted[wantedCount++] = (tr_file_index_t) i; } - (*env)->ReleaseIntArrayElements(env, unwantedIndexes, unwanted, 0); + env->ReleaseIntArrayElements(unwantedIndexes, unwanted, 0); if (wantedCount != 0) { tr_torrentSetFileDLs(tor, wanted, wantedCount, true); - err = (tr_ctorGetDeleteSource(ctor, &delete) && delete) ? 3 : 0; + err = (tr_ctorGetDeleteSource(ctor, &del) && del) ? 3 : 0; } } } else { err = 1; } } else if (!err) { - err = (tr_ctorGetDeleteSource(ctor, &delete) && delete) ? 3 : 0; + err = (tr_ctorGetDeleteSource(ctor, &del) && del) ? 3 : 0; } if ((returnMeTorrentHash != NULL) && (tor != NULL)) { const tr_info *info = tr_torrentInfo(tor); - (*env)->SetByteArrayRegion(env, returnMeTorrentHash, 0, SHA_DIGEST_LENGTH, + env->SetByteArrayRegion(returnMeTorrentHash, 0, SHA_DIGEST_LENGTH, (const jbyte *) info->hash); } @@ -125,7 +126,7 @@ Java_com_ap_transmission_btc_Native_torrentRemove( // ------------------------------------ torrentStop ------------------------------------------------ static void *torrentStop(tr_session *session, void *data, Err *err) { - tr_torrent *tor = findTorrentByIdEx(session, (int) data, err); + tr_torrent *tor = findTorrentByIdEx(session, (int) (intptr_t) data, err); tr_torrentStop(tor); CATCH: return NULL; @@ -141,7 +142,7 @@ Java_com_ap_transmission_btc_Native_torrentStop( // ------------------------------------ torrentStart ----------------------------------------------- static void *torrentStart(tr_session *session, void *data, Err *err) { - tr_torrent *tor = findTorrentByIdEx(session, (int) data, err); + tr_torrent *tor = findTorrentByIdEx(session, (int) (intptr_t) data, err); tr_torrentStart(tor); CATCH: return NULL; @@ -157,7 +158,7 @@ Java_com_ap_transmission_btc_Native_torrentStart( // ------------------------------------ torrentVerify ----------------------------------------------- static void *torrentVerify(tr_session *session, void *data, Err *err) { - tr_torrent *tor = findTorrentByIdEx(session, (int) data, err); + tr_torrent *tor = findTorrentByIdEx(session, (int) (intptr_t) data, err); tr_torrentVerify(tor, NULL, NULL); CATCH: return NULL; @@ -175,22 +176,23 @@ Java_com_ap_transmission_btc_Native_torrentVerify( JNIEXPORT jobjectArray JNICALL Java_com_ap_transmission_btc_Native_torrentListFilesFromFile( JNIEnv *env, jclass __unused c, jstring jtorrent) { +{ tr_info info; infoFromFileEx(env, 0, jtorrent, &info); tr_file_index_t count = info.fileCount; tr_file *files = info.files; - jclass jstring = (*env)->FindClass(env, "java/lang/String"); - jobjectArray a = (*env)->NewObjectArray(env, count, jstring, NULL); + jclass jstring = env->FindClass("java/lang/String"); + jobjectArray a = env->NewObjectArray(count, jstring, NULL); for (int i = 0; i < count; i++) { - jobject jname = (*env)->NewStringUTF(env, files[i].name); - (*env)->SetObjectArrayElement(env, a, i, jname); - (*env)->DeleteLocalRef(env, jname); + jobject jname = env->NewStringUTF(files[i].name); + env->SetObjectArrayElement(a, i, jname); + env->DeleteLocalRef(jname); } tr_metainfoFree(&info); return a; - CATCH: +} CATCH: return NULL; } // ------------------------------------------------------------------------------------------------- @@ -205,18 +207,19 @@ typedef struct ListFilesData { static void * torrentListFiles(tr_session *session, void *data, Err *err) { +{ ListFilesData *d = (ListFilesData *) data; tr_torrent *tor = findTorrentByIdEx(session, d->torrentId, err); d->count = tor->info.fileCount; if (d->count == 0) return NULL; - d->fileNames = malloc(d->count * (sizeof(char *))); + d->fileNames = (char **) malloc(d->count * (sizeof(char *))); for (int i = 0; i < d->count; i++) { tr_file *f = &(tor->info.files[i]); d->fileNames[i] = strdup(f->name); } - CATCH: +} CATCH: return NULL; } @@ -225,20 +228,21 @@ Java_com_ap_transmission_btc_Native_torrentListFiles( JNIEnv *env, jclass __unused c, jlong jsession, jint torrentId) { ListFilesData d = {.count=0}; jobjectArray result = NULL; +{ d.torrentId = torrentId; runInTransmissionThreadEx(env, jsession, torrentListFiles, &d); if (d.count == 0) return NULL; - result = (*env)->NewObjectArray(env, d.count, (*env)->FindClass(env, "java/lang/String"), NULL); + result = env->NewObjectArray(d.count, env->FindClass("java/lang/String"), NULL); for (int i = 0; i < d.count; i++) { - jobject jname = (*env)->NewStringUTF(env, d.fileNames[i]); - (*env)->SetObjectArrayElement(env, result, i, jname); - (*env)->DeleteLocalRef(env, jname); + jobject jname = env->NewStringUTF(d.fileNames[i]); + env->SetObjectArrayElement(result, i, jname); + env->DeleteLocalRef(jname); free(d.fileNames[i]); } free(d.fileNames); - CATCH: +} CATCH: return result; } // ------------------------------------------------------------------------------------------------- @@ -317,7 +321,7 @@ JNIEXPORT void JNICALL Java_com_ap_transmission_btc_Native_torrentMagnetToTorrentFile( JNIEnv *env, jclass __unused c, jlong jsession, jlong jsem, jstring jmagnet, jstring jpath, jint timeout, jbooleanArray enqueue) { - const char *magnet = (*env)->GetStringUTFChars(env, jmagnet, 0); + const char *magnet = env->GetStringUTFChars(jmagnet, 0); sem_t *sem = (sem_t *) jsem; MagnetToTorrentData d = {0}; d.sem = sem; @@ -325,6 +329,7 @@ Java_com_ap_transmission_btc_Native_torrentMagnetToTorrentFile( runInTransmissionThreadEx(env, jsession, torrentMagnetToTorrentFile, &d); +{ int s = 0; struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); @@ -345,21 +350,21 @@ Java_com_ap_transmission_btc_Native_torrentMagnetToTorrentFile( } } else { jboolean isCopy; - jboolean *enq = (*env)->GetBooleanArrayElements(env, enqueue, &isCopy); + jboolean *enq = env->GetBooleanArrayElements(enqueue, &isCopy); d.enqueue = enq[0]; - (*env)->ReleaseBooleanArrayElements(env, enqueue, enq, 0); + env->ReleaseBooleanArrayElements(enqueue, enq, 0); if ((d.path != NULL) && !d.enqueue) { - const char *path = (*env)->GetStringUTFChars(env, jpath, 0); + const char *path = env->GetStringUTFChars(jpath, 0); cp(env, d.path, path); - (*env)->ReleaseStringUTFChars(env, jpath, path); + env->ReleaseStringUTFChars(jpath, path); } else if (!d.enqueue) { throwEX(env, CLASS_IOEX, "Failed to download meta data"); } } - CATCH: - (*env)->ReleaseStringUTFChars(env, jmagnet, magnet); +} CATCH: + env->ReleaseStringUTFChars(jmagnet, magnet); runInTransmissionThread(__FILENAME__, __LINE__, env, jsession, torrentMagnetToTorrentFileCleanup, &d); } @@ -370,8 +375,8 @@ JNIEXPORT jint JNICALL Java_com_ap_transmission_btc_Native_torrentFindByHash( JNIEnv *env, jclass __unused c, jlong jsession, jbyteArray torrentHash) { char hash[SHA_DIGEST_LENGTH]; - (*env)->GetByteArrayRegion(env, torrentHash, 0, sizeof(hash), (jbyte *) hash); - return (jint) runInTransmissionThreadEx(env, jsession, findTorrentByHashFunc, hash); + env->GetByteArrayRegion(torrentHash, 0, sizeof(hash), (jbyte *) hash); + return (jint) (intptr_t) runInTransmissionThreadEx(env, jsession, findTorrentByHashFunc, hash); CATCH: return -1; } @@ -379,7 +384,7 @@ Java_com_ap_transmission_btc_Native_torrentFindByHash( // ------------------------------------ torrentGetName --------------------------------------------- static void *torrentGetName(tr_session *session, void *data, Err *err) { - tr_torrent *tor = findTorrentByIdEx(session, (int) data, err); + tr_torrent *tor = findTorrentByIdEx(session, (int) (intptr_t) data, err); return strdup(tor->info.name); CATCH: return NULL; @@ -391,7 +396,7 @@ Java_com_ap_transmission_btc_Native_torrentGetName( jstring jname = NULL; char *name = (char *) runInTransmissionThreadEx(env, jsession, torrentGetName, (void *) torrentId); - jname = (*env)->NewStringUTF(env, name); + jname = env->NewStringUTF(name); free(name); CATCH: return jname; @@ -416,10 +421,10 @@ JNIEXPORT void JNICALL Java_com_ap_transmission_btc_Native_torrentGetHash( JNIEnv *env, jclass __unused c, jlong jsession, jint torrentId, jbyteArray torrentHash) { GetHashData d = {torrentId}; - d.hash = (*env)->GetByteArrayElements(env, torrentHash, 0); + d.hash = env->GetByteArrayElements(torrentHash, 0); runInTransmissionThreadEx(env, jsession, torrentGetHash, &d); CATCH: - (*env)->ReleaseByteArrayElements(env, torrentHash, d.hash, 0); + env->ReleaseByteArrayElements(torrentHash, d.hash, 0); } // ------------------------------------------------------------------------------------------------- @@ -431,6 +436,7 @@ typedef struct GetPieceHashData { } GetPieceHashData; static void *torrentGetPieceHash(tr_session *session, void *data, Err *err) { +{ GetPieceHashData *d = (GetPieceHashData *) data; tr_torrent *tor = findTorrentByIdEx(session, d->torrentId, err); const tr_info *info = tr_torrentInfo(tor); @@ -440,7 +446,7 @@ static void *torrentGetPieceHash(tr_session *session, void *data, Err *err) { else err->set(err, CLASS_IAEX, "Invalid piece index: %d", d->pieceIdx); - CATCH: +} CATCH: return NULL; } @@ -449,10 +455,10 @@ Java_com_ap_transmission_btc_Native_torrentGetPieceHash( JNIEnv *env, jclass __unused c, jlong jsession, jint torrentId, jlong pieceIdx, jbyteArray pieceHash) { GetPieceHashData d = {torrentId, pieceIdx}; - d.hash = (*env)->GetByteArrayElements(env, pieceHash, 0); + d.hash = env->GetByteArrayElements(pieceHash, 0); runInTransmissionThreadEx(env, jsession, torrentGetPieceHash, &d); CATCH: - (*env)->ReleaseByteArrayElements(env, pieceHash, d.hash, 0); + env->ReleaseByteArrayElements(pieceHash, d.hash, 0); } // ------------------------------------------------------------------------------------------------- @@ -464,6 +470,7 @@ typedef struct SetPiecesHiPriData { } SetPiecesHiPriData; static void *torrentSetPiecesHiPri(tr_session *session, void *data, Err *err) { +{ SetPiecesHiPriData *d = (SetPiecesHiPriData *) data; tr_torrent *tor = findTorrentByIdEx(session, d->torrentId, err); tr_piece *pieces = tor->info.pieces; @@ -490,7 +497,7 @@ static void *torrentSetPiecesHiPri(tr_session *session, void *data, Err *err) { else tr_torrentStart(tor); } - CATCH: +} CATCH: return NULL; } @@ -522,14 +529,15 @@ static void *torrentFindFile(tr_session *session, void *data, Err *err) { JNIEXPORT jstring JNICALL Java_com_ap_transmission_btc_Native_torrentFindFile( JNIEnv *env, jclass __unused c, jlong jsession, jint torrentId, jint fileIdx) { +{ FileData d = {torrentId, fileIdx}; char *path = (char *) runInTransmissionThreadEx(env, jsession, torrentFindFile, &d); if (path == NULL) return NULL; - jstring jpath = (*env)->NewStringUTF(env, path); + jstring jpath = env->NewStringUTF(path); tr_free(path); return jpath; - CATCH: +} CATCH: return NULL; } // ------------------------------------------------------------------------------------------------- @@ -538,12 +546,13 @@ Java_com_ap_transmission_btc_Native_torrentFindFile( static void *torrentGetFileName(tr_session *session, void *data, Err *err) { FileData *d = (FileData *) data; void *name = NULL; +{ tr_torrent *tor = findTorrentByIdEx(session, d->torrentId, err); const tr_file *f = getFileInfoEx(tor, (uint32_t) d->fileIdx, err); size_t nameLen = strlen(f->name); name = calloc(nameLen + 1, sizeof(char)); memcpy(name, f->name, nameLen); - CATCH: +} CATCH: return name; } @@ -553,7 +562,7 @@ Java_com_ap_transmission_btc_Native_torrentGetFileName( jstring jname = NULL; FileData d = {torrentId, fileIdx}; char *name = (char *) runInTransmissionThreadEx(env, jsession, torrentGetFileName, &d); - jname = (*env)->NewStringUTF(env, name); + jname = env->NewStringUTF(name); free(name); CATCH: return jname; @@ -570,8 +579,13 @@ typedef struct FileStatData { static void *torrentGetFileStat(tr_session *session, void *data, Err *err) { FileStatData *d = (FileStatData *) data; - tr_torrent *tor = findTorrentByIdEx(session, d->torrentId, err); - const tr_file *f = getFileInfoEx(tor, (uint32_t) d->fileIdx, err); + tr_torrent *tor = nullptr; + const tr_file *f = nullptr; + + { + tor = findTorrentByIdEx(session, d->torrentId, err); + f = getFileInfoEx(tor, (uint32_t) d->fileIdx, err); + } CATCH: if ((tor == NULL) || (f == NULL)) return NULL; @@ -583,7 +597,7 @@ static void *torrentGetFileStat(tr_session *session, void *data, Err *err) { size_t fieldsCount = ((pieceCount % 64) == 0) ? (pieceCount / 64) : ((pieceCount / 64) + 1); jsize statLen = d->bitFieldsLen = (jsize) (fieldsCount + 6); jlong *bitFields = d->bitFields; - if (bitFields == NULL) bitFields = d->bitFields = malloc(sizeof(jlong) * statLen); + if (bitFields == NULL) bitFields = d->bitFields = (jlong *) malloc(sizeof(jlong) * statLen); tr_torrentAvailability(tor, tabs, info->pieceCount); @@ -617,19 +631,19 @@ static void *torrentGetFileStat(tr_session *session, void *data, Err *err) { * jlongArray[5] = fileComplete * jlongArray[6...] = pieceBitFields */ -JNIEXPORT jbyteArray JNICALL +JNIEXPORT jlongArray JNICALL Java_com_ap_transmission_btc_Native_torrentGetFileStat( - JNIEnv *env, jclass __unused c, jlong jsession, jint torrentId, jint fileIdx, jbyteArray stat) { + JNIEnv *env, jclass __unused c, jlong jsession, jint torrentId, jint fileIdx, jlongArray stat) { FileStatData d = {.torrentId= torrentId, .fileIdx = fileIdx, .bitFields = NULL}; - if (stat != NULL) d.bitFields = (*env)->GetLongArrayElements(env, stat, 0); + if (stat != NULL) d.bitFields = env->GetLongArrayElements(stat, 0); runInTransmissionThreadEx(env, jsession, torrentGetFileStat, &d); if (stat == NULL) { - stat = (*env)->NewLongArray(env, d.bitFieldsLen); - (*env)->SetLongArrayRegion(env, stat, 0, d.bitFieldsLen, d.bitFields); + stat = env->NewLongArray(d.bitFieldsLen); + env->SetLongArrayRegion(stat, 0, d.bitFieldsLen, d.bitFields); free(d.bitFields); } else { - (*env)->ReleaseLongArrayElements(env, stat, d.bitFields, 0); + env->ReleaseLongArrayElements(stat, d.bitFields, 0); } CATCH: @@ -675,10 +689,10 @@ JNIEXPORT void JNICALL Java_com_ap_transmission_btc_Native_torrentGetPiece( JNIEnv *env, jclass __unused c, jlong jsession, jint torrentId, jlong pieceIdx, jbyteArray dst, jint offset, jint len) { - GetPieceData d = {torrentId, pieceIdx, (*env)->GetByteArrayElements(env, dst, 0), offset, len}; + GetPieceData d = {torrentId, pieceIdx, env->GetByteArrayElements(dst, 0), offset, len}; runInTransmissionThreadEx(env, jsession, torrentGetPiece, &d); CATCH: - (*env)->ReleaseByteArrayElements(env, dst, d.dst, 0); + env->ReleaseByteArrayElements(dst, d.dst, 0); } // ------------------------------------------------------------------------------------------------- @@ -716,44 +730,45 @@ typedef struct StatBriefData { */ static void *torrentStatBrief(tr_session *session, void *data, __unused Err *err) { StatBriefData *d = (StatBriefData *) data; - tr_torrent *it = session->torrentList; - int numTorrents = session->torrentCount; + int numTorrents = session->torrents.size(); int statLen = numTorrents * 10; if ((d->stat == NULL) || (statLen != d->statLen)) { - d->stat = malloc(sizeof(jlong) * statLen); + d->stat = (jlong *) malloc(sizeof(jlong) * statLen); d->statLen = statLen; d->alloc = true; } - for (int i = 0; (it != NULL); it = it->next, i += 10) { - tr_torrent_activity status = tr_torrentGetActivity(it); + int i = -10; + for (auto tor : session->torrents) { + i += 10; + tr_torrent_activity status = tr_torrentGetActivity(tor); - d->stat[i] = it->uniqueId; - d->stat[i + 3] = tr_cpSizeWhenDone(&it->completion); - d->stat[i + 4] = tr_torrentGetLeftUntilDone(it); - d->stat[i + 5] = it->uploadedCur + it->uploadedPrev; + d->stat[i] = tor->uniqueId; + d->stat[i + 3] = tr_cpSizeWhenDone(&tor->completion); + d->stat[i + 4] = tr_torrentGetLeftUntilDone(tor); + d->stat[i + 5] = tor->uploadedCur + tor->uploadedPrev; - if (it->error != TR_STAT_LOCAL_ERROR) { + if (tor->error != TR_STAT_LOCAL_ERROR) { switch (status) { case TR_STATUS_STOPPED: d->stat[i + 1] = 0; - d->stat[i + 2] = (jlong) (tr_cpPercentDone(&it->completion) * 100); + d->stat[i + 2] = (jlong) (tr_cpPercentDone(&tor->completion) * 100); continue; case TR_STATUS_CHECK_WAIT : case TR_STATUS_CHECK: d->stat[i + 1] = 1; - d->stat[i + 2] = (jlong) (getVerifyProgress(it) * 100); + d->stat[i + 2] = (jlong) (getVerifyProgress(tor) * 100); break; case TR_STATUS_DOWNLOAD_WAIT: case TR_STATUS_DOWNLOAD : d->stat[i + 1] = 2; - d->stat[i + 2] = (jlong) (tr_cpPercentDone(&it->completion) * 100); + d->stat[i + 2] = (jlong) (tr_cpPercentDone(&tor->completion) * 100); break; case TR_STATUS_SEED_WAIT : case TR_STATUS_SEED: d->stat[i + 1] = 3; - d->stat[i + 2] = (jlong) (tr_cpPercentDone(&it->completion) * 100); + d->stat[i + 2] = (jlong) (tr_cpPercentDone(&tor->completion) * 100); break; } } else { @@ -761,14 +776,14 @@ static void *torrentStatBrief(tr_session *session, void *data, __unused Err *err d->stat[i + 2] = 0; } - if (it->swarm != NULL) { + if (tor->swarm != NULL) { uint64_t const now = tr_time_msec(); struct tr_swarm_stats sstat; - tr_swarmGetStats(it->swarm, &sstat); + tr_swarmGetStats(tor->swarm, &sstat); d->stat[i + 6] = sstat.activePeerCount[TR_UP]; d->stat[i + 7] = sstat.activePeerCount[TR_DOWN] + sstat.activeWebseedCount; - d->stat[i + 8] = tr_bandwidthGetPieceSpeed_Bps(&it->bandwidth, now, TR_UP); - d->stat[i + 9] = tr_bandwidthGetPieceSpeed_Bps(&it->bandwidth, now, TR_DOWN); + d->stat[i + 8] = tor->bandwidth->getPieceSpeedBytesPerSecond(now, TR_UP); + d->stat[i + 9] = tor->bandwidth->getPieceSpeedBytesPerSecond(now, TR_DOWN); } else { d->stat[i + 6] = 0L; d->stat[i + 7] = 0L; @@ -788,8 +803,8 @@ Java_com_ap_transmission_btc_Native_torrentStatBrief( bool release = false; if (jstat != NULL) { - d.stat = stat = (*env)->GetLongArrayElements(env, jstat, 0); - d.statLen = (*env)->GetArrayLength(env, jstat); + d.stat = stat = env->GetLongArrayElements(jstat, 0); + d.statLen = env->GetArrayLength(jstat); release = true; } @@ -797,11 +812,11 @@ Java_com_ap_transmission_btc_Native_torrentStatBrief( CATCH: if (release) { - (*env)->ReleaseLongArrayElements(env, jstat, stat, 0); + env->ReleaseLongArrayElements(jstat, stat, 0); } if (d.alloc) { - jstat = (*env)->NewLongArray(env, d.statLen); - (*env)->SetLongArrayRegion(env, jstat, 0, d.statLen, d.stat); + jstat = env->NewLongArray(d.statLen); + env->SetLongArrayRegion(jstat, 0, d.statLen, d.stat); free(d.stat); } @@ -811,7 +826,7 @@ Java_com_ap_transmission_btc_Native_torrentStatBrief( // ------------------------------------- torrentGetError ------------------------------------------- static void *torrentGetError(tr_session *session, void *data, Err *err) { - tr_torrent *tor = findTorrentByIdEx(session, (int) data, err); + tr_torrent *tor = findTorrentByIdEx(session, (int) (intptr_t) data, err); return strdup(tor->errorString); CATCH: return NULL; @@ -820,12 +835,13 @@ static void *torrentGetError(tr_session *session, void *data, Err *err) { JNIEXPORT jstring JNICALL Java_com_ap_transmission_btc_Native_torrentGetError( JNIEnv *env, jclass __unused c, jlong jsession, jint torrentId) { +{ char *err = (char *) runInTransmissionThreadEx(env, jsession, torrentGetError, (void *) torrentId); - jstring jerr = (*env)->NewStringUTF(env, err); + jstring jerr = env->NewStringUTF(err); free(err); return jerr; - CATCH: +} CATCH: return NULL; } // ------------------------------------------------------------------------------------------------- @@ -852,11 +868,11 @@ Java_com_ap_transmission_btc_Native_torrentSetDnd( SetDndData d; d.dnd = dnd; d.torrentId = torrentId; - d.files = (tr_file_index_t *) (*env)->GetIntArrayElements(env, files, 0); - d.fileCount = (tr_file_index_t) (*env)->GetArrayLength(env, files); + d.files = (tr_file_index_t *) env->GetIntArrayElements(files, 0); + d.fileCount = (tr_file_index_t) env->GetArrayLength(files); runInTransmissionThreadEx(env, jsession, torrentSetDnd, &d); CATCH: - (*env)->ReleaseIntArrayElements(env, files, (jint *) d.files, 0); + env->ReleaseIntArrayElements(files, (jint *) d.files, 0); } // ------------------------------------------------------------------------------------------------- @@ -878,10 +894,11 @@ JNIEXPORT void JNICALL Java_com_ap_transmission_btc_Native_torrentSetLocation( JNIEnv *env, jclass __unused c, jlong jsession, jint torrentId, jstring jpath) { jboolean isCopy; - const char *path = (*env)->GetStringUTFChars(env, jpath, &isCopy); + const char *path = env->GetStringUTFChars(jpath, &isCopy); SetLocationData d = {torrentId, path}; runInTransmissionThreadEx(env, jsession, torrentSetLocation, &d); CATCH: - (*env)->ReleaseStringUTFChars(env, jpath, path); + env->ReleaseStringUTFChars(jpath, path); } // ------------------------------------------------------------------------------------------------- +} // extern "C" \ No newline at end of file diff --git a/src/main/cpp/transmission.c b/src/main/cpp/transmission.cc similarity index 81% rename from src/main/cpp/transmission.c rename to src/main/cpp/transmission.cc index ec45604..108bbcf 100644 --- a/src/main/cpp/transmission.c +++ b/src/main/cpp/transmission.cc @@ -23,9 +23,11 @@ #define SPEED_G_STR "GB/s" #define SPEED_T_STR "TB/s" +extern "C" { + JNIEXPORT jstring JNICALL Java_com_ap_transmission_btc_Native_transmissionVersion(JNIEnv *env, jclass __unused c) { - return (*env)->NewStringUTF(env, SHORT_VERSION_STRING); + return env->NewStringUTF(SHORT_VERSION_STRING); } // ----------------------------------------- Start ------------------------------------------------- @@ -51,11 +53,11 @@ static tr_rpc_callback_status rpcFunc(tr_session *__unused session, tr_rpc_callb switch (type) { case TR_RPC_TORRENT_ADDED: if ((tor != NULL) && !tr_torrentHasMetadata(tor)) { - tr_torrent_metadata_func mdFunc = tor->metadata_func_user_data; + tr_torrent_metadata_func mdFunc = (tr_torrent_metadata_func) tor->metadata_func_user_data; OrigMetaDataFunc *origData = NULL; if (mdFunc != NULL) { - origData = malloc(sizeof(OrigMetaDataFunc)); + origData = (OrigMetaDataFunc *) malloc(sizeof(OrigMetaDataFunc)); origData->metadata_func = mdFunc; origData->metadata_func_user_data = tor->metadata_func_user_data; } @@ -88,7 +90,7 @@ static void altSpeedFunc(tr_session *__unused session, bool __unused active, boo } static void *transmissionStart(tr_session *session, void *data, Err *__unused err) { - jboolean suspend = (jboolean) data; + jboolean suspend = (jboolean) (intptr_t) data; tr_sessionSetPaused(session, false); tr_sessionSetRPCCallback(session, rpcFunc, NULL); tr_sessionSetAltSpeedFunc(session, altSpeedFunc, NULL); @@ -114,7 +116,7 @@ Java_com_ap_transmission_btc_Native_transmissionStart( jboolean loadConfig, jboolean enableSequential, jboolean suspend) { tr_variant settings; tr_session *session; - const char *configDir = (*env)->GetStringUTFChars(env, jconfigDir, 0); + const char *configDir = env->GetStringUTFChars(jconfigDir, 0); tr_variantInitDict(&settings, 0); if (loadConfig) { @@ -130,9 +132,9 @@ Java_com_ap_transmission_btc_Native_transmissionStart( tr_variantDictAddBool(&settings, TR_KEY_peer_port_random_on_start, true); } - const char *downloadsDir = (*env)->GetStringUTFChars(env, jdownloadsDir, 0); + const char *downloadsDir = env->GetStringUTFChars(jdownloadsDir, 0); tr_variantDictAddStr(&settings, TR_KEY_download_dir, downloadsDir); - (*env)->ReleaseStringUTFChars(env, jdownloadsDir, downloadsDir); + env->ReleaseStringUTFChars(jdownloadsDir, downloadsDir); tr_variantDictAddInt(&settings, TR_KEY_encryption, encrMode); tr_variantDictAddBool(&settings, TR_KEY_sequentialDownload, enableSequential); @@ -142,21 +144,21 @@ Java_com_ap_transmission_btc_Native_transmissionStart( tr_variantDictAddInt(&settings, TR_KEY_rpc_port, rpcPort); if (enableAuth) { - const char *username = (*env)->GetStringUTFChars(env, jusername, 0); - const char *password = (*env)->GetStringUTFChars(env, jpassword, 0); + const char *username = env->GetStringUTFChars(jusername, 0); + const char *password = env->GetStringUTFChars(jpassword, 0); tr_variantDictAddStr(&settings, TR_KEY_rpc_username, username); tr_variantDictAddStr(&settings, TR_KEY_rpc_password, password); - (*env)->ReleaseStringUTFChars(env, jusername, username); - (*env)->ReleaseStringUTFChars(env, jpassword, password); + env->ReleaseStringUTFChars(jusername, username); + env->ReleaseStringUTFChars(jpassword, password); tr_variantDictAddBool(&settings, TR_KEY_rpc_authentication_required, true); } else { tr_variantDictAddBool(&settings, TR_KEY_rpc_authentication_required, false); } if (enableRpcWhitelist) { - const char *rpcWhitelist = (*env)->GetStringUTFChars(env, jrpcWhitelist, 0); + const char *rpcWhitelist = env->GetStringUTFChars(jrpcWhitelist, 0); tr_variantDictAddStr(&settings, TR_KEY_rpc_whitelist, rpcWhitelist); - (*env)->ReleaseStringUTFChars(env, jrpcWhitelist, rpcWhitelist); + env->ReleaseStringUTFChars(jrpcWhitelist, rpcWhitelist); tr_variantDictAddBool(&settings, TR_KEY_rpc_whitelist_enabled, true); } else { tr_variantDictAddBool(&settings, TR_KEY_rpc_whitelist_enabled, false); @@ -170,11 +172,11 @@ Java_com_ap_transmission_btc_Native_transmissionStart( tr_formatter_speed_init(SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR); session = tr_sessionInit(configDir, true, &settings); tr_sessionSaveSettings(session, configDir, &settings); - (*env)->ReleaseStringUTFChars(env, jconfigDir, configDir); + env->ReleaseStringUTFChars(jconfigDir, configDir); tr_variantFree(&settings); jlong jsession = (jlong) session; - runInTransmissionThreadEx(env, jsession, transmissionStart, (void *) suspend); + runInTransmissionThreadEx(env, jsession, transmissionStart, (void *) (intptr_t) suspend); return jsession; CATCH: @@ -193,9 +195,9 @@ Java_com_ap_transmission_btc_Native_transmissionStop( tr_variantInitDict(&settings, 0); tr_sessionGetSettings(session, &settings); - const char *configDir = (*env)->GetStringUTFChars(env, jconfigDir, 0); + const char *configDir = env->GetStringUTFChars(jconfigDir, 0); tr_sessionSaveSettings(session, configDir, &settings); - (*env)->ReleaseStringUTFChars(env, jconfigDir, configDir); + env->ReleaseStringUTFChars(jconfigDir, configDir); tr_variantFree(&settings); tr_sessionClose(session); @@ -211,7 +213,7 @@ static void *transmissionSuspend(tr_session *session, void *data, Err *__unused JNIEXPORT void JNICALL Java_com_ap_transmission_btc_Native_transmissionSuspend( JNIEnv *__unused env, jclass __unused c, jlong jsession, jboolean suspend) { - runInTransmissionThreadEx(env, jsession, transmissionSuspend, (void *) suspend); + runInTransmissionThreadEx(env, jsession, transmissionSuspend, (void *) (intptr_t) suspend); CATCH:; } // ------------------------------------------------------------------------------------------------- @@ -219,8 +221,8 @@ Java_com_ap_transmission_btc_Native_transmissionSuspend( // -------------------------------- HasDownloadingTorrents ----------------------------------------- static void * transmissionHasDownloadingTorrents(tr_session *session, void *__unused data, Err *__unused err) { - for (tr_torrent *it = session->torrentList; it != NULL; it = it->next) { - switch (tr_torrentGetActivity(it)) { + for (auto tor : session->torrents) { + switch (tr_torrentGetActivity(tor)) { case TR_STATUS_DOWNLOAD: case TR_STATUS_DOWNLOAD_WAIT: case TR_STATUS_CHECK: @@ -237,8 +239,8 @@ transmissionHasDownloadingTorrents(tr_session *session, void *__unused data, Err JNIEXPORT jboolean JNICALL Java_com_ap_transmission_btc_Native_transmissionHasDownloadingTorrents( JNIEnv *__unused env, jclass __unused c, jlong jsession) { - return (jboolean) runInTransmissionThreadEx(env, jsession, transmissionHasDownloadingTorrents, - NULL); + return (jboolean) (intptr_t) runInTransmissionThreadEx(env, jsession, + transmissionHasDownloadingTorrents, NULL); CATCH: return JNI_FALSE; } @@ -253,17 +255,17 @@ typedef struct ListTorrentsData { static void * transmissionListTorrentNames(tr_session *session, void *data, Err *__unused err) { ListTorrentsData *d = (ListTorrentsData *) data; - d->count = session->torrentCount; + d->count = session->torrents.size(); if (d->count == 0) return NULL; - d->torrents = malloc(d->count * (sizeof(char *))); + d->torrents = (char **) malloc(d->count * (sizeof(char *))); int i = 0; - for (tr_torrent *it = session->torrentList; it != NULL; it = it->next) { - size_t hashLen = sizeof(it->info.hashString); - size_t nameLen = strlen(it->info.name); + for (auto tor : session->torrents) { + size_t hashLen = sizeof(tor->info.hashString); + size_t nameLen = strlen(tor->info.name); size_t lineLen = hashLen + nameLen + 12; - char *line = malloc(lineLen); - snprintf(line, lineLen, "%d %s %s", tr_torrentId(it), it->info.hashString, it->info.name); + char *line = (char *) malloc(lineLen); + snprintf(line, lineLen, "%d %s %s", tr_torrentId(tor), tor->info.hashString, tor->info.name); d->torrents[i++] = line; } @@ -278,12 +280,12 @@ Java_com_ap_transmission_btc_Native_transmissionListTorrentNames( runInTransmissionThreadEx(env, jsession, transmissionListTorrentNames, &d); if (d.count == 0) return NULL; - result = (*env)->NewObjectArray(env, d.count, (*env)->FindClass(env, "java/lang/String"), NULL); + result = env->NewObjectArray(d.count, env->FindClass("java/lang/String"), NULL); for (int i = 0; i < d.count; i++) { - jobject jname = (*env)->NewStringUTF(env, d.torrents[i]); - (*env)->SetObjectArrayElement(env, result, i, jname); - (*env)->DeleteLocalRef(env, jname); + jobject jname = env->NewStringUTF(d.torrents[i]); + env->SetObjectArrayElement(result, i, jname); + env->DeleteLocalRef(jname); free(d.torrents[i]); } @@ -299,4 +301,6 @@ Java_com_ap_transmission_btc_Native_transmissionGetEncryptionMode( JNIEnv *__unused env, jclass __unused c, jlong jsession) { return tr_sessionGetEncryption((tr_session *) jsession); } -// ------------------------------------------------------------------------------------------------- \ No newline at end of file +// ------------------------------------------------------------------------------------------------- + +} //extern "C" \ No newline at end of file diff --git a/src/main/java/com/ap/transmission/btc/Utils.java b/src/main/java/com/ap/transmission/btc/Utils.java index d50f14e..4a24a09 100644 --- a/src/main/java/com/ap/transmission/btc/Utils.java +++ b/src/main/java/com/ap/transmission/btc/Utils.java @@ -1,5 +1,12 @@ package com.ap.transmission.btc; +import static android.content.Context.CONNECTIVITY_SERVICE; +import static android.content.Context.WIFI_SERVICE; +import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static android.net.ConnectivityManager.TYPE_WIFI; +import static com.ap.transmission.btc.BuildConfig.BUILD_TYPE; +import static java.net.NetworkInterface.getNetworkInterfaces; + import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; @@ -7,6 +14,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; import android.net.ConnectivityManager; @@ -76,13 +84,6 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import static android.content.Context.CONNECTIVITY_SERVICE; -import static android.content.Context.WIFI_SERVICE; -import static android.net.ConnectivityManager.TYPE_ETHERNET; -import static android.net.ConnectivityManager.TYPE_WIFI; -import static com.ap.transmission.btc.BuildConfig.BUILD_TYPE; -import static java.net.NetworkInterface.getNetworkInterfaces; - /** * @author Andrey Pavlenko */ @@ -846,4 +847,15 @@ public static void openUri(Activity a, Uri uri, String mime) { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); a.startActivity(Intent.createChooser(intent, a.getResources().getString(R.string.open_with))); } + + public static boolean hasManifestPermission(Context ctx, String perm) { + try { + String[] perms = ctx.getPackageManager(). + getPackageInfo(ctx.getPackageName(), PackageManager.GET_PERMISSIONS) + .requestedPermissions; + return Arrays.asList(perms).contains(perm); + } catch (PackageManager.NameNotFoundException ignore) { + return false; + } + } } diff --git a/src/main/java/com/ap/transmission/btc/activities/SelectFileActivity.java b/src/main/java/com/ap/transmission/btc/activities/SelectFileActivity.java index 6f07bbb..ac30633 100644 --- a/src/main/java/com/ap/transmission/btc/activities/SelectFileActivity.java +++ b/src/main/java/com/ap/transmission/btc/activities/SelectFileActivity.java @@ -12,6 +12,7 @@ import static com.ap.transmission.btc.Utils.showErr; import static com.ap.transmission.btc.Utils.showMsg; +import android.Manifest; import android.annotation.SuppressLint; import android.app.AlertDialog; import android.app.ListActivity; @@ -112,7 +113,9 @@ protected void onCreate(Bundle savedInstanceState) { } } - if ((SDK_INT >= VERSION_CODES.R) && !Environment.isExternalStorageManager()) { + if ((SDK_INT >= VERSION_CODES.R) + && Utils.hasManifestPermission(this, Manifest.permission.MANAGE_EXTERNAL_STORAGE) + && !Environment.isExternalStorageManager()) { Intent req = new Intent(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); Uri uri = Uri.fromParts("package", getPackageName(), null); req.setData(uri); diff --git a/src/main/java/com/ap/transmission/btc/torrent/Transmission.java b/src/main/java/com/ap/transmission/btc/torrent/Transmission.java index e26b679..1981eba 100644 --- a/src/main/java/com/ap/transmission/btc/torrent/Transmission.java +++ b/src/main/java/com/ap/transmission/btc/torrent/Transmission.java @@ -55,627 +55,631 @@ * @author Andrey Pavlenko */ public class Transmission { - private static final byte STATE_STOPPED = 0; - private static final byte STATE_STARTING = -1; - private static final byte STATE_STOPPING = -2; - private static final String TAG = Transmission.class.getName(); - private static final String SETTINGS_FILE = "settings.json"; - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private final Prefs prefs; - private List watchers; - private PowerLock powerLock; - private SsdpServer ssdpServer; - private volatile List semaphores; - private volatile HttpServer httpServer; - private volatile TorrentFs torrentFs; - private volatile ExecutorService executor; - private volatile ScheduledExecutorService scheduler; - private volatile long session = STATE_STOPPED; - private volatile byte suspended; - - public Transmission(Prefs prefs) { - this.prefs = prefs; - } - - public static String getVersion() { - return Native.transmissionVersion(); - } - - public Prefs getPrefs() { - return prefs; - } - - public Context getContext() { - return getPrefs().getContext(); - } - - public Lock readLock() { - return lock.readLock(); - } - - public Lock writeLock() { - return lock.writeLock(); - } - - long getSession() { - return session; - } - - public TorrentFs getTorrentFs() { - TorrentFs fs = torrentFs; - - if (fs == null) { - throw new IllegalStateException("Transmission is not running"); - } - - return fs; - } - - public HttpServer getHttpServer() throws IllegalStateException, IOException { - HttpServer s = httpServer; - - if (s == null) { - writeLock().lock(); - try { - if ((s = httpServer) == null) { - checkRunning(); - httpServer = s = new SimpleHttpServer(this); - s.start(); - } - } finally { - writeLock().unlock(); - } - } - - return s; - } - - public void start() throws IOException { - if (isRunning()) return; - - writeLock().lock(); - if (isRunning()) return; - session = STATE_STARTING; - boolean ok = false; - getExecutor(); // Start executor - - try { - Context ctx = getContext(); - boolean suspend = false; - File dataDir = new File(ctx.getApplicationInfo().dataDir); - File configDir = new File(prefs.getSettingsDir()); - File downloadDir = new File(prefs.getDownloadDir()); - File webDir = new File(dataDir, "web"); - File settings = new File(configDir, SETTINGS_FILE); - File tmp = new File(dataDir, "tmp"); - File indexHtml = new File(dataDir, "web/index.html"); - File indexOrigHtml = prefs.isAltWebEnabled() ? new File(dataDir, "web/index.webcontrol.html") - : new File(dataDir, "web/index.original.html"); - mkdirs(configDir, downloadDir, tmp); - - copyAssets(ctx.getAssets(), "web", dataDir, true); - if (indexHtml.length() != indexOrigHtml.length()) Utils.transfer(indexOrigHtml, indexHtml); - - if (prefs.isWifiEthOnly()) { - suspend = !Utils.isWifiEthActive(ctx, prefs.getWifiSsid()); - ConnectivityChangeReceiver.register(ctx); - } - - Native.envSet("TMP", tmp.getAbsolutePath()); - Native.envSet("TRANSMISSION_WEB_HOME", webDir.getAbsolutePath()); - increaseSoBuf(); - configureProxy(prefs); - - session = Native.transmissionStart(configDir.getAbsolutePath(), - downloadDir.getAbsolutePath(), prefs.getEncryptionMode().ordinal(), - prefs.isRpcEnabled(), prefs.getRpcPort(), - prefs.isRpcAuthEnabled(), prefs.getRpcUsername(), prefs.getRpcPassword(), - prefs.isRpcWhitelistEnabled(), prefs.getRpcWhitelist(), settings.exists(), - prefs.isSeqDownloadEnabled(), suspend); - suspended = (byte) (suspend ? 1 : 0); - debug(TAG, "Session created: %d", session); - torrentFs = new TorrentFs(this, session); - - startWatchers(); - startUpnp(); - if (hasDownloadingTorrents()) wakeLock(); - - // Handle callbacks in a separate thread to avoid dead locks - Native.transmissionSetRpcCallbacks( - () -> getExecutor().submit(this::torrentAddedOrChanged), - () -> getExecutor().submit(this::torrentRemoved), - () -> getExecutor().submit(this::sessionChanged), - () -> getExecutor().submit(this::cheduledAltSpeed)); - - ok = true; - } finally { - writeLock().unlock(); - if (!ok) stop(); - } - } - - private void startWatchers() { - if (prefs.isWatchDirEnabled()) { - int interval = prefs.getWatchInterval(); - watchers = new ArrayList<>(); - - for (Map.Entry e : prefs.getWatchDirs().entrySet()) { - startWatcher(e.getKey(), e.getValue()); - } - - if (interval > 0) { + private static final byte STATE_STOPPED = 0; + private static final byte STATE_STARTING = 1; + private static final byte STATE_RUNNING = 2; + private static final byte STATE_STOPPING = 4; + private static final byte STATE_SUSPENDED = 8; + private static final byte STATE_SUSPENDED_BY_USER = 16; + private static final String TAG = Transmission.class.getName(); + private static final String SETTINGS_FILE = "settings.json"; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Prefs prefs; + private List watchers; + private PowerLock powerLock; + private SsdpServer ssdpServer; + private volatile List semaphores; + private volatile HttpServer httpServer; + private volatile TorrentFs torrentFs; + private volatile ExecutorService executor; + private volatile ScheduledExecutorService scheduler; + private volatile long session; + private volatile byte state = STATE_STOPPED; + + public Transmission(Prefs prefs) { + this.prefs = prefs; + } + + public static String getVersion() { + return Native.transmissionVersion(); + } + + public Prefs getPrefs() { + return prefs; + } + + public Context getContext() { + return getPrefs().getContext(); + } + + public Lock readLock() { + return lock.readLock(); + } + + public Lock writeLock() { + return lock.writeLock(); + } + + long getSession() { + return session; + } + + public TorrentFs getTorrentFs() { + TorrentFs fs = torrentFs; + + if (fs == null) { + throw new IllegalStateException("Transmission is not running"); + } + + return fs; + } + + public HttpServer getHttpServer() throws IllegalStateException, IOException { + HttpServer s = httpServer; + + if (s == null) { + writeLock().lock(); + try { + if ((s = httpServer) == null) { + checkRunning(); + httpServer = s = new SimpleHttpServer(this); + s.start(); + } + } finally { + writeLock().unlock(); + } + } + + return s; + } + + public void start() throws IOException { + if (isRunning()) return; + + writeLock().lock(); + if (isRunning()) return; + state = STATE_STARTING; + boolean ok = false; + getExecutor(); // Start executor + + try { + Context ctx = getContext(); + boolean suspend = false; + File dataDir = new File(ctx.getApplicationInfo().dataDir); + File configDir = new File(prefs.getSettingsDir()); + File downloadDir = new File(prefs.getDownloadDir()); + File webDir = new File(dataDir, "web"); + File settings = new File(configDir, SETTINGS_FILE); + File tmp = new File(dataDir, "tmp"); + File indexHtml = new File(dataDir, "web/index.html"); + File indexOrigHtml = prefs.isAltWebEnabled() ? new File(dataDir, "web/index.webcontrol.html") + : new File(dataDir, "web/index.original.html"); + mkdirs(configDir, downloadDir, tmp); + + copyAssets(ctx.getAssets(), "web", dataDir, true); + if (indexHtml.length() != indexOrigHtml.length()) Utils.transfer(indexOrigHtml, indexHtml); + + if (prefs.isWifiEthOnly()) { + suspend = !Utils.isWifiEthActive(ctx, prefs.getWifiSsid()); + ConnectivityChangeReceiver.register(ctx); + } + + Native.envSet("TMP", tmp.getAbsolutePath()); + Native.envSet("TRANSMISSION_WEB_HOME", webDir.getAbsolutePath()); + increaseSoBuf(); + configureProxy(prefs); + + session = Native.transmissionStart(configDir.getAbsolutePath(), + downloadDir.getAbsolutePath(), prefs.getEncryptionMode().ordinal(), + prefs.isRpcEnabled(), prefs.getRpcPort(), + prefs.isRpcAuthEnabled(), prefs.getRpcUsername(), prefs.getRpcPassword(), + prefs.isRpcWhitelistEnabled(), prefs.getRpcWhitelist(), settings.exists(), + prefs.isSeqDownloadEnabled(), suspend); + state = suspend ? (STATE_RUNNING | STATE_SUSPENDED) : STATE_RUNNING; + debug(TAG, "Session created: %d", session); + torrentFs = new TorrentFs(this, session); + + startWatchers(); + startUpnp(); + if (hasDownloadingTorrents()) wakeLock(); + + // Handle callbacks in a separate thread to avoid dead locks + Native.transmissionSetRpcCallbacks( + () -> getExecutor().submit(this::torrentAddedOrChanged), + () -> getExecutor().submit(this::torrentRemoved), + () -> getExecutor().submit(this::sessionChanged), + () -> getExecutor().submit(this::cheduledAltSpeed)); + + ok = true; + } finally { + writeLock().unlock(); + if (!ok) stop(); + } + } + + private void startWatchers() { + if (prefs.isWatchDirEnabled()) { + int interval = prefs.getWatchInterval(); + watchers = new ArrayList<>(); + + for (Map.Entry e : prefs.getWatchDirs().entrySet()) { + startWatcher(e.getKey(), e.getValue()); + } + + if (interval > 0) { ScheduledExecutorService sched = getScheduler(); sched.scheduleWithFixedDelay(() -> { if (!isRunning() || (watchers == null)) return; for (Watcher w : watchers) w.scan(); }, interval, interval, TimeUnit.SECONDS); } - } - } - - private void startWatcher(String watchDir, String downloadDir) { - File wd = new File(watchDir); - mkdirs(wd); - Watcher w = new Watcher(wd, new File(downloadDir)); - w.startWatching(); - watchers.add(w); - } - - private void startUpnp() { - if (!prefs.isUpnpEnabled()) return; - if ((ssdpServer != null) && ssdpServer.isRunning()) return; - HttpServer httpServer; - - try { - httpServer = getHttpServer(); - } catch (IOException ex) { - err(TAG, ex, "Failed to start HTTP Server"); - return; - } - - try { - if (ssdpServer == null) ssdpServer = new SsdpServer(httpServer); - ssdpServer.start(); - } catch (IOException ex) { - err(TAG, ex, "Failed to start SSDP server, SSDP NOTIFY will be sent every 60 seconds"); - } - } - - public void stop() { - long s = session; - if (s <= 0) return; - - writeLock().lock(); - try { - if ((s = session) <= 0) return; - session = STATE_STOPPING; - - try { - if (semaphores != null) for (Long sem : semaphores) Native.semPost(sem); - if (watchers != null) for (Watcher w : watchers) w.stopWatching(); - ConnectivityChangeReceiver.unregister(getContext()); - Native.transmissionSetRpcCallbacks(null, null, null, null); - Utils.close(httpServer, ssdpServer); - stopExecutor(); - stopSheduler(); - - debug(TAG, "Closing session: %d", s); - File configDir = new File(prefs.getSettingsDir()); - mkdirs(configDir); - Native.transmissionStop(s, configDir.getAbsolutePath()); - } finally { - session = STATE_STOPPED; - torrentFs = null; - httpServer = null; - ssdpServer = null; - watchers = null; - executor = null; - scheduler = null; - semaphores = null; - suspended = 0; - wakeUnlock(); - } - } finally { - writeLock().unlock(); - } - } - - public boolean isRunning() { - return session > 0; - } - - @SuppressWarnings("unused") - public boolean isStarting() { - return session == STATE_STARTING; - } - - @SuppressWarnings("unused") - public boolean isStopping() { - return session == STATE_STOPPING; - } - - public boolean isStopped() { - return session == STATE_STOPPED; - } - - @SuppressLint("StaticFieldLeak") - public void suspend(final boolean suspend, final boolean byUser, final Runnable callback) { - checkRunning(); - new AsyncTask() { - - @Override - protected Void doInBackground(Void... voids) { - writeLock().lock(); - try { - if (!isRunning()) return null; - debug(TAG, "Suspending: %s, by user: %s", suspend, byUser); - - if (suspend) { - Utils.close(ssdpServer); - ssdpServer = null; - } else { - startUpnp(); - } - - Native.transmissionSuspend(session, suspend); - suspended = (byte) (!suspend ? 0 : byUser ? 2 : 1); - } finally { - writeLock().unlock(); - } - - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - TransmissionService.updateNotification(); - if (callback != null) callback.run(); - } - }. - - execute(); - } - - public boolean isSuspended() { - return suspended != 0; - } - - public boolean isSuspendedByUser() { - return suspended == 2; - } - - public boolean hasDownloadingTorrents() { - readLock().lock(); - try { - return isRunning() && Native.transmissionHasDownloadingTorrents(session); - } finally { - readLock().unlock(); - } - } - - public AddTorrentResult addTorrent(File torrentFile, File downloadDir, - @Nullable int[] unwantedIndexes, - @Nullable byte[] returnMeTorrentHash, - boolean delete, boolean sequential, - int retries, int delay) throws InterruptedException { - if (!isRunning()) return NOT_STARTED; - String path = torrentFile.getAbsolutePath(); - String downloadPath = downloadDir.getAbsolutePath(); - info(TAG, "Adding new torrent file: %s", path); - mkdirs(downloadDir); - - for (int i = 0; i < retries + 1; i++) { - int result; - - readLock().lock(); - try { - if (!isRunning()) { - info(TAG, "Transmission is not running - ignoring: %s", path); - return NOT_STARTED; - } - - result = Native.torrentAdd(session, path, downloadPath, delete, sequential, - unwantedIndexes, returnMeTorrentHash); - } finally { - readLock().unlock(); - } - - switch (result) { - case 2: - info(TAG, "Duplicate torrent - ignoring: %s", torrentFile); - torrentAddedOrChanged(); - return DUPLICATE; - case 0: - torrentAddedOrChanged(); - return OK; - case 3: - torrentAddedOrChanged(); - return OK_DELETE; - case 1: - Thread.sleep(delay); - } - } - - err(TAG, "Failed to parse torrent file: %s", torrentFile); - return PARSE_ERR; - } - - private void torrentAddedOrChanged() { - debug(TAG, "torrentAddedOrChanged()"); - TorrentFs fs = torrentFs; - if (fs != null) fs.reset(); - wakeLock(); - } - - private void torrentRemoved() { - debug(TAG, "torrentAddedOrChanged()"); - TorrentFs fs = torrentFs; - if (fs != null) fs.reset(); - } - - private void sessionChanged() { - debug(TAG, "torrentAddedOrChanged()"); - readLock().lock(); - try { - if (!isRunning()) return; - int encrMode = Native.transmissionGetEncryptionMode(session); - - if (encrMode != prefs.getEncryptionMode().ordinal()) { - prefs.setEncryptionMode(EncrMode.get(encrMode)); - } - } finally { - readLock().unlock(); - } - } - - private void cheduledAltSpeed() { - debug(TAG, "Alt speed changed by timer"); - wakeLock(); - } - - public Promise magnetToTorrent(final Uri magnetLink, final File destTorrentPath, - final int timeout, final boolean[] enqueue) { - return new Promise() { - private final long sem = Native.semCreate(); - - { - List semaphores = Transmission.this.semaphores; - - if (semaphores == null) { - writeLock().lock(); - try { - if ((semaphores = Transmission.this.semaphores) == null) { - Transmission.this.semaphores = semaphores = new Vector<>(); - } - } finally { - writeLock().unlock(); - } - } - - semaphores.add(sem); - } - - @Override - public Void get() throws Throwable { - readLock().lock(); - try { - checkRunning(); - TorrentFs fs = torrentFs; - if (fs != null) fs.reset(); - Native.torrentMagnetToTorrentFile(session, sem, magnetLink.toString(), - destTorrentPath.getAbsolutePath(), timeout, enqueue); - return null; - } finally { - readLock().unlock(); - } - } - - @Override - public synchronized void cancel() { - Native.semPost(sem); - List semaphores = Transmission.this.semaphores; - if (semaphores != null) semaphores.remove(sem); - } - - @Override - protected void finalize() { - List semaphores = Transmission.this.semaphores; - if (semaphores != null) semaphores.remove(sem); - Native.semDestroy(sem); - } - }; - } - - private void increaseSoBuf() { - if (!prefs.isIncreaseSoBuf()) return; - String file = "scripts/set_so_buf.sh"; - debug(TAG, "Executing su -c %s", file); - AssetManager amgr = getContext().getAssets(); - InputStream in = null; - - try { - in = amgr.open(file, AssetManager.ACCESS_STREAMING); - int status = Utils.su(3000, in); - if (status != 0) err(TAG, "su -c %s failed with exit code %d", file, status); - } catch (IOException ex) { - err(TAG, ex, "Failed to open asset: %s", file); - } finally { - if (in != null) try { - in.close(); - } catch (IOException ignore) { - } - } - } - - public ExecutorService getExecutor() { - ExecutorService exec = executor; - - if (exec == null) { - writeLock().lock(); - try { - if ((exec = executor) == null) { - executor = exec = new ThreadPoolExecutor(0, - 30, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), - Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); - } - } finally { - writeLock().unlock(); - } - } - - return exec; - } - - private void stopExecutor() { - ExecutorService exec = executor; - if (exec == null) return; - executor = null; - - try { - exec.shutdownNow(); - exec.awaitTermination(30, TimeUnit.SECONDS); - } catch (InterruptedException ignore) { - } - } - - public ScheduledExecutorService getScheduler() { - ScheduledExecutorService sched = scheduler; - - if (sched == null) { - writeLock().lock(); - try { - if ((sched = scheduler) == null) { - checkRunning(); - scheduler = sched = Executors.newScheduledThreadPool(1); - } - } finally { - writeLock().unlock(); - } - } - - return sched; - } - - private void stopSheduler() { - ScheduledExecutorService sched = scheduler; - if (sched == null) return; - scheduler = null; - - try { - sched.shutdownNow(); - sched.awaitTermination(30, TimeUnit.SECONDS); - } catch (InterruptedException ignore) { - } - } - - @SuppressLint("WakelockTimeout") - private void wakeLock() { - writeLock().lock(); - try { - if (powerLock != null || !isRunning()) return; - PowerLock pl = PowerLock.newLock(getContext()); - if (pl == null) return; - pl.acquire(); - powerLock = pl; - debug(TAG, "WakeLock acquired"); - - final ScheduledFuture[] f = new ScheduledFuture[1]; - f[0] = getScheduler().scheduleWithFixedDelay(() -> { - if (!hasDownloadingTorrents()) { - writeLock().lock(); - try { - if (!isRunning()) { - wakeUnlock(); - f[0].cancel(false); - } else if (!Native.transmissionHasDownloadingTorrents(session)) { - debug(TAG, "No active downloads - releasing WakeLock"); - wakeUnlock(); - f[0].cancel(false); - } - } finally { - writeLock().unlock(); - } - } - }, 1, 1, TimeUnit.MINUTES); - } finally { - writeLock().unlock(); - } - } - - private void wakeUnlock() { - writeLock().lock(); - try { - if (powerLock == null) return; - powerLock.release(); - powerLock = null; - debug(TAG, "WakeLock released"); - } finally { - writeLock().unlock(); - } - } - - private final class Watcher extends FileObserver { - private final File dir; - private final File downloadDir; - - private Watcher(File dir, File downloadDir) { - super(dir.getAbsolutePath(), FileObserver.CREATE); - this.dir = dir; - this.downloadDir = downloadDir; - } - - @Override - public void startWatching() { - info(TAG, "Start watching directory: %s", dir); - mkdirs(dir); - scan(); - super.startWatching(); - } - - void scan() { - String[] files = dir.list(); - if (files != null) for (String f : files) add(f); - } - - @Override - public void onEvent(int event, @Nullable String path) { - add(path); - } - - private void add(String path) { - if ((path == null) || !path.endsWith(".torrent")) return; - File f = new File(dir, path); - AddTorrentResult result; - - try { - result = addTorrent(f, downloadDir, null, null, - false, prefs.isSeqDownloadEnabled(), 10, 1000); - } catch (InterruptedException ex) { - err(TAG, ex, "Failed to add torrent file: %s", f); - return; - } - - if (result == OK) { - File renameTo = new File(dir, path + ".added"); - if (!f.renameTo(renameTo) && - !StorageAccess.renamePath(f.getAbsolutePath(), renameTo.getAbsolutePath())) { - err(TAG, "Failed to rename file to: %s", renameTo); - } - } else if (result != NOT_STARTED) { - if (!f.delete() && !StorageAccess.removePath(f.getAbsolutePath())) { - err(TAG, "Failed to delete file: %s", f); - } - } - } - } - - void checkRunning() { - if (!isRunning()) { - throw new IllegalStateException("Transmission is not running"); - } - } - - public enum AddTorrentResult { - OK, PARSE_ERR, DUPLICATE, OK_DELETE, NOT_STARTED - } + } + } + + private void startWatcher(String watchDir, String downloadDir) { + File wd = new File(watchDir); + mkdirs(wd); + Watcher w = new Watcher(wd, new File(downloadDir)); + w.startWatching(); + watchers.add(w); + } + + private void startUpnp() { + if (!prefs.isUpnpEnabled()) return; + if ((ssdpServer != null) && ssdpServer.isRunning()) return; + HttpServer httpServer; + + try { + httpServer = getHttpServer(); + } catch (IOException ex) { + err(TAG, ex, "Failed to start HTTP Server"); + return; + } + + try { + if (ssdpServer == null) ssdpServer = new SsdpServer(httpServer); + ssdpServer.start(); + } catch (IOException ex) { + err(TAG, ex, "Failed to start SSDP server, SSDP NOTIFY will be sent every 60 seconds"); + } + } + + public void stop() { + long s = session; + if (s == 0) return; + + writeLock().lock(); + try { + if ((s = session) == 0) return; + state = STATE_STOPPING; + + try { + if (semaphores != null) for (Long sem : semaphores) Native.semPost(sem); + if (watchers != null) for (Watcher w : watchers) w.stopWatching(); + ConnectivityChangeReceiver.unregister(getContext()); + Native.transmissionSetRpcCallbacks(null, null, null, null); + Utils.close(httpServer, ssdpServer); + stopExecutor(); + stopSheduler(); + + debug(TAG, "Closing session: %d", s); + File configDir = new File(prefs.getSettingsDir()); + mkdirs(configDir); + Native.transmissionStop(s, configDir.getAbsolutePath()); + } finally { + session = 0; + state = STATE_STOPPED; + torrentFs = null; + httpServer = null; + ssdpServer = null; + watchers = null; + executor = null; + scheduler = null; + semaphores = null; + wakeUnlock(); + } + } finally { + writeLock().unlock(); + } + } + + public boolean isRunning() { + return (state & STATE_RUNNING) != 0; + } + + @SuppressWarnings("unused") + public boolean isStarting() { + return (state & STATE_STARTING) != 0; + } + + @SuppressWarnings("unused") + public boolean isStopping() { + return (state & STATE_STOPPING) != 0; + } + + public boolean isStopped() { + return state == STATE_STOPPED; + } + + @SuppressLint("StaticFieldLeak") + public void suspend(final boolean suspend, final boolean byUser, final Runnable callback) { + checkRunning(); + new AsyncTask() { + + @Override + protected Void doInBackground(Void... voids) { + writeLock().lock(); + try { + if (!isRunning()) return null; + debug(TAG, "Suspending: %s, by user: %s", suspend, byUser); + + if (suspend) { + Utils.close(ssdpServer); + ssdpServer = null; + } else { + startUpnp(); + } + + Native.transmissionSuspend(session, suspend); + state &= ~(STATE_SUSPENDED | STATE_SUSPENDED_BY_USER); + if (suspend) state |= byUser ? STATE_SUSPENDED_BY_USER : STATE_SUSPENDED; + } finally { + writeLock().unlock(); + } + + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + TransmissionService.updateNotification(); + if (callback != null) callback.run(); + } + }. + + execute(); + } + + public boolean isSuspended() { + return (state & (STATE_SUSPENDED | STATE_SUSPENDED_BY_USER)) != 0; + } + + public boolean isSuspendedByUser() { + return (state & STATE_SUSPENDED_BY_USER) != 0; + } + + public boolean hasDownloadingTorrents() { + readLock().lock(); + try { + return isRunning() && Native.transmissionHasDownloadingTorrents(session); + } finally { + readLock().unlock(); + } + } + + public AddTorrentResult addTorrent(File torrentFile, File downloadDir, + @Nullable int[] unwantedIndexes, + @Nullable byte[] returnMeTorrentHash, + boolean delete, boolean sequential, + int retries, int delay) throws InterruptedException { + if (!isRunning()) return NOT_STARTED; + String path = torrentFile.getAbsolutePath(); + String downloadPath = downloadDir.getAbsolutePath(); + info(TAG, "Adding new torrent file: %s", path); + mkdirs(downloadDir); + + for (int i = 0; i < retries + 1; i++) { + int result; + + readLock().lock(); + try { + if (!isRunning()) { + info(TAG, "Transmission is not running - ignoring: %s", path); + return NOT_STARTED; + } + + result = Native.torrentAdd(session, path, downloadPath, delete, sequential, + unwantedIndexes, returnMeTorrentHash); + } finally { + readLock().unlock(); + } + + switch (result) { + case 2: + info(TAG, "Duplicate torrent - ignoring: %s", torrentFile); + torrentAddedOrChanged(); + return DUPLICATE; + case 0: + torrentAddedOrChanged(); + return OK; + case 3: + torrentAddedOrChanged(); + return OK_DELETE; + case 1: + Thread.sleep(delay); + } + } + + err(TAG, "Failed to parse torrent file: %s", torrentFile); + return PARSE_ERR; + } + + private void torrentAddedOrChanged() { + debug(TAG, "torrentAddedOrChanged()"); + TorrentFs fs = torrentFs; + if (fs != null) fs.reset(); + wakeLock(); + } + + private void torrentRemoved() { + debug(TAG, "torrentAddedOrChanged()"); + TorrentFs fs = torrentFs; + if (fs != null) fs.reset(); + } + + private void sessionChanged() { + debug(TAG, "torrentAddedOrChanged()"); + readLock().lock(); + try { + if (!isRunning()) return; + int encrMode = Native.transmissionGetEncryptionMode(session); + + if (encrMode != prefs.getEncryptionMode().ordinal()) { + prefs.setEncryptionMode(EncrMode.get(encrMode)); + } + } finally { + readLock().unlock(); + } + } + + private void cheduledAltSpeed() { + debug(TAG, "Alt speed changed by timer"); + wakeLock(); + } + + public Promise magnetToTorrent(final Uri magnetLink, final File destTorrentPath, + final int timeout, final boolean[] enqueue) { + return new Promise() { + private final long sem = Native.semCreate(); + + { + List semaphores = Transmission.this.semaphores; + + if (semaphores == null) { + writeLock().lock(); + try { + if ((semaphores = Transmission.this.semaphores) == null) { + Transmission.this.semaphores = semaphores = new Vector<>(); + } + } finally { + writeLock().unlock(); + } + } + + semaphores.add(sem); + } + + @Override + public Void get() throws Throwable { + readLock().lock(); + try { + checkRunning(); + TorrentFs fs = torrentFs; + if (fs != null) fs.reset(); + Native.torrentMagnetToTorrentFile(session, sem, magnetLink.toString(), + destTorrentPath.getAbsolutePath(), timeout, enqueue); + return null; + } finally { + readLock().unlock(); + } + } + + @Override + public synchronized void cancel() { + Native.semPost(sem); + List semaphores = Transmission.this.semaphores; + if (semaphores != null) semaphores.remove(sem); + } + + @Override + protected void finalize() { + List semaphores = Transmission.this.semaphores; + if (semaphores != null) semaphores.remove(sem); + Native.semDestroy(sem); + } + }; + } + + private void increaseSoBuf() { + if (!prefs.isIncreaseSoBuf()) return; + String file = "scripts/set_so_buf.sh"; + debug(TAG, "Executing su -c %s", file); + AssetManager amgr = getContext().getAssets(); + InputStream in = null; + + try { + in = amgr.open(file, AssetManager.ACCESS_STREAMING); + int status = Utils.su(3000, in); + if (status != 0) err(TAG, "su -c %s failed with exit code %d", file, status); + } catch (IOException ex) { + err(TAG, ex, "Failed to open asset: %s", file); + } finally { + if (in != null) try { + in.close(); + } catch (IOException ignore) { + } + } + } + + public ExecutorService getExecutor() { + ExecutorService exec = executor; + + if (exec == null) { + writeLock().lock(); + try { + if ((exec = executor) == null) { + executor = exec = new ThreadPoolExecutor(0, + 30, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), + Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); + } + } finally { + writeLock().unlock(); + } + } + + return exec; + } + + private void stopExecutor() { + ExecutorService exec = executor; + if (exec == null) return; + executor = null; + + try { + exec.shutdownNow(); + exec.awaitTermination(30, TimeUnit.SECONDS); + } catch (InterruptedException ignore) { + } + } + + public ScheduledExecutorService getScheduler() { + ScheduledExecutorService sched = scheduler; + + if (sched == null) { + writeLock().lock(); + try { + if ((sched = scheduler) == null) { + checkRunning(); + scheduler = sched = Executors.newScheduledThreadPool(1); + } + } finally { + writeLock().unlock(); + } + } + + return sched; + } + + private void stopSheduler() { + ScheduledExecutorService sched = scheduler; + if (sched == null) return; + scheduler = null; + + try { + sched.shutdownNow(); + sched.awaitTermination(30, TimeUnit.SECONDS); + } catch (InterruptedException ignore) { + } + } + + @SuppressLint("WakelockTimeout") + private void wakeLock() { + writeLock().lock(); + try { + if (powerLock != null || !isRunning()) return; + PowerLock pl = PowerLock.newLock(getContext()); + if (pl == null) return; + pl.acquire(); + powerLock = pl; + debug(TAG, "WakeLock acquired"); + + final ScheduledFuture[] f = new ScheduledFuture[1]; + f[0] = getScheduler().scheduleWithFixedDelay(() -> { + if (!hasDownloadingTorrents()) { + writeLock().lock(); + try { + if (!isRunning()) { + wakeUnlock(); + f[0].cancel(false); + } else if (!Native.transmissionHasDownloadingTorrents(session)) { + debug(TAG, "No active downloads - releasing WakeLock"); + wakeUnlock(); + f[0].cancel(false); + } + } finally { + writeLock().unlock(); + } + } + }, 1, 1, TimeUnit.MINUTES); + } finally { + writeLock().unlock(); + } + } + + private void wakeUnlock() { + writeLock().lock(); + try { + if (powerLock == null) return; + powerLock.release(); + powerLock = null; + debug(TAG, "WakeLock released"); + } finally { + writeLock().unlock(); + } + } + + private final class Watcher extends FileObserver { + private final File dir; + private final File downloadDir; + + private Watcher(File dir, File downloadDir) { + super(dir.getAbsolutePath(), FileObserver.CREATE); + this.dir = dir; + this.downloadDir = downloadDir; + } + + @Override + public void startWatching() { + info(TAG, "Start watching directory: %s", dir); + mkdirs(dir); + scan(); + super.startWatching(); + } + + void scan() { + String[] files = dir.list(); + if (files != null) for (String f : files) add(f); + } + + @Override + public void onEvent(int event, @Nullable String path) { + add(path); + } + + private void add(String path) { + if ((path == null) || !path.endsWith(".torrent")) return; + File f = new File(dir, path); + AddTorrentResult result; + + try { + result = addTorrent(f, downloadDir, null, null, + false, prefs.isSeqDownloadEnabled(), 10, 1000); + } catch (InterruptedException ex) { + err(TAG, ex, "Failed to add torrent file: %s", f); + return; + } + + if (result == OK) { + File renameTo = new File(dir, path + ".added"); + if (!f.renameTo(renameTo) && + !StorageAccess.renamePath(f.getAbsolutePath(), renameTo.getAbsolutePath())) { + err(TAG, "Failed to rename file to: %s", renameTo); + } + } else if (result != NOT_STARTED) { + if (!f.delete() && !StorageAccess.removePath(f.getAbsolutePath())) { + err(TAG, "Failed to delete file: %s", f); + } + } + } + } + + void checkRunning() { + if (!isRunning()) { + throw new IllegalStateException("Transmission is not running"); + } + } + + public enum AddTorrentResult { + OK, PARSE_ERR, DUPLICATE, OK_DELETE, NOT_STARTED + } }