1
0
mirror of https://framagit.org/tom79/fedilab-tube synced 2025-06-05 21:09:11 +02:00

22 Commits

Author SHA1 Message Date
7bc018314c try 2021-02-05 18:23:55 +01:00
a04d27dd4c try 2021-02-05 18:12:18 +01:00
5533929cc5 try 2021-02-05 11:35:14 +01:00
8ae8467791 try 2021-02-05 10:41:56 +01:00
c2225bca37 try 2021-02-05 10:28:31 +01:00
d4270a3ef9 try 2021-02-05 09:59:31 +01:00
cd68de6274 try 2021-02-05 09:18:34 +01:00
c5866b1acf try 2021-02-05 08:29:52 +01:00
bd1794a10e try 2021-02-05 07:43:53 +01:00
f120d8fad5 try 2021-02-02 18:03:24 +01:00
70e8133350 lib 2021-02-01 18:38:49 +01:00
2cdabbcb70 Release 1.13.1 2021-01-23 17:23:53 +01:00
d5ad9b181b Release 1.13.1 2021-01-23 17:14:32 +01:00
540fb3e2dc Update ReadMe remove useless accounts 2021-01-23 17:07:58 +01:00
edbe65593b Some fixes 2021-01-23 17:03:26 +01:00
d5f394dfea Fix issue with Manifest merging 2021-01-17 10:29:13 +01:00
d5a5fdf52e Release 1.13.0 2021-01-16 18:25:59 +01:00
a7f9256947 Small fixes 2021-01-16 14:14:15 +01:00
d769729901 Merge branch 'l10n_develop' into 'develop'
New Crowdin updates

See merge request tom79/fedilab-tube!64
2021-01-16 11:34:44 +01:00
b2026c8784 New Crowdin updates 2021-01-16 11:34:44 +01:00
b29de141ef Add cast library for Google release only - Checked through Exodus 2021-01-16 11:33:59 +01:00
6d4772da75 Fix issue #170 2021-01-11 17:42:54 +01:00
476 changed files with 122177 additions and 785 deletions

View File

@ -1,133 +0,0 @@
# This file is a template, and might need editing before it works on your project.
# Read more about this script on this blog post https://about.gitlab.com/2018/10/24/setting-up-gitlab-ci-for-android-projects/, by Jason Lenny
# If you are interested in using Android with FastLane for publishing take a look at the Android-Fastlane template.
image: openjdk:8-jdk
variables:
# ANDROID_COMPILE_SDK is the version of Android you're compiling with.
# It should match compileSdkVersion.
ANDROID_COMPILE_SDK: "30"
# ANDROID_BUILD_TOOLS is the version of the Android build tools you are using.
# It should match buildToolsVersion.
ANDROID_BUILD_TOOLS: "30.0.2"
# It's what version of the command line tools we're going to download from the official site.
# Official Site-> https://developer.android.com/studio/index.html
# There, look down below at the cli tools only, sdk tools package is of format:
# commandlinetools-os_type-ANDROID_SDK_TOOLS_latest.zip
# when the script was last modified for latest compileSdkVersion, it was which is written down below
ANDROID_SDK_TOOLS: "6609375"
# Packages installation before running script
before_script:
- apt-get --quiet update --yes
- apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1
# Setup path as android_home for moving/exporting the downloaded sdk into it
- export ANDROID_HOME="${PWD}/android-home"
# Create a new directory at specified location
- install -d $ANDROID_HOME
# Here we are installing androidSDK tools from official source,
# (the key thing here is the url from where you are downloading these sdk tool for command line, so please do note this url pattern there and here as well)
# after that unzipping those tools and
# then running a series of SDK manager commands to install necessary android SDK packages that'll allow the app to build
- wget --output-document=$ANDROID_HOME/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}_latest.zip
# move to the archive at ANDROID_HOME
- pushd $ANDROID_HOME
- unzip -d cmdline-tools cmdline-tools.zip
- popd
- export PATH=$PATH:${ANDROID_HOME}/cmdline-tools/tools/bin/
# Nothing fancy here, just checking sdkManager version
- sdkmanager --version
# use yes to accept all licenses
- yes | sdkmanager --sdk_root=${ANDROID_HOME} --licenses || true
- sdkmanager --sdk_root=${ANDROID_HOME} "platforms;android-${ANDROID_COMPILE_SDK}"
- sdkmanager --sdk_root=${ANDROID_HOME} "platform-tools"
- sdkmanager --sdk_root=${ANDROID_HOME} "build-tools;${ANDROID_BUILD_TOOLS}"
# Not necessary, but just for surity
- chmod +x ./gradle
stages:
- build
- build-and-test
- tag
.no-upload: &no-upload
stage: build-and-test
retry: 2
# Make Project
assembleDebug:
<<: *no-upload
cache:
key: "${CI_COMMIT_TAG}"
paths:
- app/build/outputs/apk/fdroid_peertube_apps_educ/debug/app-fdroid_peertube_apps_educ-debug.apk
- app/build/outputs/apk/fdroid_full/debug/app-fdroid_full-debug.apk
policy: push
script:
- ./gradlew assembleDebug
# Basic android and gradle stuff
# Check linting
lintfdroid_peertube_apps_educDebug:
interruptible: true
stage: build
script:
- ./gradlew -Pci --console=plain :app:lintfdroid_peertube_apps_educDebug -PbuildDir=lint
except:
- tags
lintFdroid_fullDebug:
interruptible: true
stage: build
script:
- ./gradlew -Pci --console=plain :app:lintFdroid_fullDebug -PbuildDir=lint
except:
- tags
## PROTECTED VARIABLES TO SET IN GITLAB:
# - GITLAB_API_TOKEN: token you create on Gitlab
# - NC_REMOTE_DIR: like https://YOUR_NEXTCLOUD/remote.php/dav/files/YOUR_USER/mastalab (no trailing slash)
# - NC_SHARE_URL: share the folder in Nextcloud with public link and put your public link here (no trailing slash)
# - NC_USER: nextcloud user
# - NC_PASSWORD: nextcloud password
## Protect all tags in Gitlab repo settings (do a wildcard, ie '*')
# For now, it uses the assembleDebug builds, you'll need to create a job in build-and-test to create the apks, with only: - tags and add except: - tags to assembleDebug (like in debugTests)
# In it, put something like this to get your signature key file:
# - curl -s --output signature.jsk -u "${NC_USER}:${NC_PASSWORD}" "https://YOUR_NEXTCLOUD/remote.php/dav/files/YOUR_USER/signature.jsk"
putApkOnTags:
image: hatsoftwares/curl-jq:latest
stage: tag
retry: 2
cache:
key: "${CI_COMMIT_TAG}"
paths:
- app/build/outputs/apk/fdroid_peertube_apps_educ/debug/app-fdroid_peertube_apps_educ-debug.apk
- app/build/outputs/apk/fdroid_full/debug/app-fdroid_full-debug.apk
policy: pull
script:
- export PROJECT_API_URL="https://framagit.org/api/v4/projects/${CI_PROJECT_ID}"
- export DESCRIPTION_URL="${PROJECT_API_URL}/repository/tags/${CI_COMMIT_TAG}"
- export RELEASE_URL="${DESCRIPTION_URL}/release"
- export NC_UPLOAD_URL="${NC_REMOTE_DIR}/${CI_COMMIT_TAG}"
- export NC_DOWNLOAD_URL="${NC_SHARE_URL}/download?path=%2F${CI_COMMIT_TAG}%2F&files="
- 'export HEADER="Private-Token: ${GITLAB_API_TOKEN}"'
- export acadUrl="${NC_DOWNLOAD_URL}app-fdroid_peertube_apps_educ-debug.apk"
- export fullUrl="${NC_DOWNLOAD_URL}app-fdroid_full-debug.apk"
- 'curl -s -u "${NC_USER}:${NC_PASSWORD}" -X MKCOL "${NC_UPLOAD_URL}"'
- 'curl -s -u "${NC_USER}:${NC_PASSWORD}" -T app/build/outputs/apk/fdroid_full/debug/app-fdroid_full-debug.apk "${NC_UPLOAD_URL}/app-fdroid_full-debug.apk"'
- 'curl -s -u "${NC_USER}:${NC_PASSWORD}" -T app/build/outputs/apk/fdroid_peertube_apps_educ/debug/app-fdroid_peertube_apps_educ-debug.apk "${NC_UPLOAD_URL}/app-fdroid_peertube_apps_educ-debug.apk"'
- export description=$(curl -s --header "${HEADER}" "${DESCRIPTION_URL}" | jq .release.description | sed -e 's@"@@g')
- if [[ $description == 'null' ]]; then export METHOD="POST"; echo -e "[Get the acad version](${acadUrl})\n\n[Get the full version](${fullUrl})" > /tmp/text; fi
- if [[ $description != 'null' ]]; then export METHOD="PUT"; echo -e "${description}\n\n[Get the acad version](${acadUrl})\n\n[Get the full version](${fullUrl})" > /tmp/text; fi
- curl -s --request $METHOD --data-urlencode "description@/tmp/text" --header "${HEADER}" "${RELEASE_URL}"
only:
- tags

View File

@ -10,7 +10,7 @@ Crowdin will not pick up changes in develop branch, that's why all translations
### Issues: ### Issues:
Issues are handled on Github at: https://github.com/stom79/TubeLab/issues, before opening an issue, please check it has not yet been submitted by someone else Issues are handled on Framagit at: https://framagit.org/imattau/fedilab-tube/-/issues, before opening an issue, please check it has not yet been submitted by someone else
### Contribution to code: ### Contribution to code:

View File

@ -11,8 +11,8 @@ android {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 30
versionCode 37 versionCode 39
versionName "1.12.1" versionName "1.13.1"
multiDexEnabled true multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@ -42,7 +42,7 @@ android {
} }
//boolean full_instances if set to false means TubeAcad //boolean full_instances if set to false means TubeAcad
productFlavors { productFlavors {
fdroid_peertube_apps_educ { fdroid_acad {
applicationId "app.fedilab.fedilabtube" applicationId "app.fedilab.fedilabtube"
resValue "string", "app_name", "TubeAcad" resValue "string", "app_name", "TubeAcad"
resValue "string", "app_id", "app.fedilab.fedilabtube" resValue "string", "app_id", "app.fedilab.fedilabtube"
@ -53,8 +53,11 @@ android {
buildConfigField "boolean", "sepia_search", "false" buildConfigField "boolean", "sepia_search", "false"
buildConfigField "boolean", "instance_switcher", "true" buildConfigField "boolean", "instance_switcher", "true"
buildConfigField "boolean", "allow_remote_connections", "false" buildConfigField "boolean", "allow_remote_connections", "false"
buildConfigField "boolean", "google_cast_lib", "false"
buildConfigField "int", "cast_enabled", "0"
buildConfigField "int", "default_theme", "2"
} }
google_peertube_apps_educ { google_acad {
applicationId "app.fedilab.fedilabtube" applicationId "app.fedilab.fedilabtube"
resValue "string", "app_name", "TubeAcad" resValue "string", "app_name", "TubeAcad"
resValue "string", "app_id", "app.fedilab.fedilabtube" resValue "string", "app_id", "app.fedilab.fedilabtube"
@ -65,6 +68,9 @@ android {
buildConfigField "boolean", "sepia_search", "false" buildConfigField "boolean", "sepia_search", "false"
buildConfigField "boolean", "instance_switcher", "true" buildConfigField "boolean", "instance_switcher", "true"
buildConfigField "boolean", "allow_remote_connections", "false" buildConfigField "boolean", "allow_remote_connections", "false"
buildConfigField "boolean", "google_cast_lib", "true"
buildConfigField "int", "cast_enabled", "1"
buildConfigField "int", "default_theme", "2"
} }
fdroid_full { fdroid_full {
applicationId "app.fedilab.tubelab" applicationId "app.fedilab.tubelab"
@ -77,6 +83,9 @@ android {
buildConfigField "boolean", "sepia_search", "true" buildConfigField "boolean", "sepia_search", "true"
buildConfigField "boolean", "instance_switcher", "true" buildConfigField "boolean", "instance_switcher", "true"
buildConfigField "boolean", "allow_remote_connections", "true" buildConfigField "boolean", "allow_remote_connections", "true"
buildConfigField "boolean", "google_cast_lib", "false"
buildConfigField "int", "cast_enabled", "0"
buildConfigField "int", "default_theme", "2"
} }
google_full { google_full {
applicationId "app.fedilab.tubelab" applicationId "app.fedilab.tubelab"
@ -89,6 +98,9 @@ android {
buildConfigField "boolean", "sepia_search", "true" buildConfigField "boolean", "sepia_search", "true"
buildConfigField "boolean", "instance_switcher", "true" buildConfigField "boolean", "instance_switcher", "true"
buildConfigField "boolean", "allow_remote_connections", "true" buildConfigField "boolean", "allow_remote_connections", "true"
buildConfigField "boolean", "google_cast_lib", "true"
buildConfigField "int", "cast_enabled", "1"
buildConfigField "int", "default_theme", "2"
} }
queermotion { queermotion {
applicationId "org.queermotion.peertube" applicationId "org.queermotion.peertube"
@ -101,6 +113,9 @@ android {
buildConfigField "boolean", "sepia_search", "false" buildConfigField "boolean", "sepia_search", "false"
buildConfigField "boolean", "instance_switcher", "false" buildConfigField "boolean", "instance_switcher", "false"
buildConfigField "boolean", "allow_remote_connections", "false" buildConfigField "boolean", "allow_remote_connections", "false"
buildConfigField "boolean", "google_cast_lib", "false"
buildConfigField "int", "cast_enabled", "0"
buildConfigField "int", "default_theme", "2"
} }
bittube { bittube {
applicationId "app.fedilab.bittube" applicationId "app.fedilab.bittube"
@ -113,33 +128,37 @@ android {
buildConfigField "boolean", "sepia_search", "false" buildConfigField "boolean", "sepia_search", "false"
buildConfigField "boolean", "instance_switcher", "true" buildConfigField "boolean", "instance_switcher", "true"
buildConfigField "boolean", "allow_remote_connections", "false" buildConfigField "boolean", "allow_remote_connections", "false"
buildConfigField "boolean", "google_cast_lib", "true"
buildConfigField "int", "cast_enabled", "1"
buildConfigField "int", "default_theme", "1"
} }
} }
sourceSets { sourceSets {
fdroid_peertube_apps_educ {
res.srcDirs = ['src/main/res', 'src/acad/res'] fdroid_acad {
java.srcDirs = ['src/main/java', 'src/acad/java', 'src/no_google_donation/java'] res.srcDirs = ['src/main/res', 'src/acad/res', 'src/no_google_cast_lib/res']
java.srcDirs = ['src/main/java', 'src/acad/java', 'src/no_google_donation/java', 'src/no_google_cast_lib/java']
} }
google_peertube_apps_educ { google_acad {
res.srcDirs = ['src/main/res', 'src/acad/res'] res.srcDirs = ['src/main/res', 'src/acad/res', 'src/google_cast_lib/res']
java.srcDirs = ['src/main/java', 'src/acad/java', 'src/no_google_donation/java'] java.srcDirs = ['src/main/java', 'src/acad/java', 'src/no_google_donation/java', 'src/google_cast_lib/java']
} }
fdroid_full { fdroid_full {
res.srcDirs = ['src/main/res', 'src/full/res'] res.srcDirs = ['src/main/res', 'src/full/res', 'src/no_google_cast_lib/res']
java.srcDirs = ['src/main/java', 'src/full/java', 'src/no_google_donation/java'] java.srcDirs = ['src/main/java', 'src/full/java', 'src/no_google_donation/java', 'src/no_google_cast_lib/java']
} }
google_full { google_full {
res.srcDirs = ['src/main/res', 'src/full/res', 'src/google_donation/res'] res.srcDirs = ['src/main/res', 'src/full/res', 'src/google_donation/res', 'src/google_cast_lib/res']
java.srcDirs = ['src/main/java', 'src/full/java', 'src/google_donation/java'] java.srcDirs = ['src/main/java', 'src/full/java', 'src/google_donation/java', 'src/google_cast_lib/java']
} }
queermotion { queermotion {
res.srcDirs = ['src/main/res', 'src/queermotion/res'] res.srcDirs = ['src/main/res', 'src/queermotion/res', 'src/no_google_cast_lib/res']
java.srcDirs = ['src/main/java', 'src/full/java', 'src/no_google_donation/java'] java.srcDirs = ['src/main/java', 'src/full/java', 'src/no_google_donation/java', 'src/no_google_cast_lib/java']
} }
bittube { bittube {
res.srcDirs = ['src/main/res', 'src/bittube/res'] res.srcDirs = ['src/main/res', 'src/bittube/res', 'src/google_cast_lib/res']
java.srcDirs = ['src/main/java', 'src/full/java', 'src/no_google_donation/java'] java.srcDirs = ['src/main/java', 'src/full/java', 'src/no_google_donation/java', 'src/google_cast_lib/java']
} }
} }
} }
@ -168,6 +187,7 @@ dependencies {
implementation 'androidx.browser:browser:1.3.0' implementation 'androidx.browser:browser:1.3.0'
implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.documentfile:documentfile:1.0.1'
implementation project(path: ':torrentStream') implementation project(path: ':torrentStream')
implementation project(path: ':frostwire-jlibtorrent')
testImplementation 'junit:junit:4.13.1' testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
@ -179,6 +199,7 @@ dependencies {
implementation "com.github.mabbas007:TagsEditText:1.0.5" implementation "com.github.mabbas007:TagsEditText:1.0.5"
implementation "com.github.bumptech.glide:glide:4.11.0" implementation "com.github.bumptech.glide:glide:4.11.0"
annotationProcessor "com.github.bumptech.glide:compiler:4.11.0" annotationProcessor "com.github.bumptech.glide:compiler:4.11.0"
implementation 'jp.wasabeef:glide-transformations:4.0.0'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "net.gotev:uploadservice:4.5.1" implementation "net.gotev:uploadservice:4.5.1"
implementation "net.gotev:uploadservice-okhttp:4.5.1" implementation "net.gotev:uploadservice-okhttp:4.5.1"
@ -196,17 +217,34 @@ dependencies {
implementation "androidx.work:work-runtime:2.4.0" implementation "androidx.work:work-runtime:2.4.0"
implementation "androidx.work:work-runtime-ktx:2.4.0" implementation "androidx.work:work-runtime-ktx:2.4.0"
//custom cast feature
implementation 'jp.wasabeef:glide-transformations:4.0.0'
implementation 'su.litvak.chromecast:api-v2:0.11.3'
implementation 'com.fasterxml.jackson.core:jackson-core:2.12.0'
implementation 'org.slf4j:slf4j-simple:1.7.30'
//************ DONATION GOOGLE ONLY **************//
google_fullImplementation "com.android.billingclient:billing:3.0.2" google_fullImplementation "com.android.billingclient:billing:3.0.2"
fdroid_peertube_apps_educImplementation 'org.matomo.sdk:tracker:4.1.2' //************ MATOMO --> acad instances only **************//
google_peertube_apps_educImplementation 'org.matomo.sdk:tracker:4.1.2'
fdroid_acadImplementation 'org.matomo.sdk:tracker:4.1.2'
google_acadImplementation 'org.matomo.sdk:tracker:4.1.2'
//************ CAST **************///
//---> Google libs (google_full + bittube)
google_acadImplementation "androidx.mediarouter:mediarouter:1.2.1"
google_acadImplementation 'com.google.android.gms:play-services-cast-framework:19.0.0'
google_fullImplementation "androidx.mediarouter:mediarouter:1.2.1"
google_fullImplementation 'com.google.android.gms:play-services-cast-framework:19.0.0'
bittubeImplementation "androidx.mediarouter:mediarouter:1.2.1"
bittubeImplementation 'com.google.android.gms:play-services-cast-framework:19.0.0'
//----> Other flavors
fdroid_acadImplementation 'su.litvak.chromecast:api-v2:0.11.3'
fdroid_acadImplementation 'com.fasterxml.jackson.core:jackson-core:2.12.0'
fdroid_acadImplementation 'org.slf4j:slf4j-simple:1.7.30'
fdroid_fullImplementation 'su.litvak.chromecast:api-v2:0.11.3'
fdroid_fullImplementation 'com.fasterxml.jackson.core:jackson-core:2.12.0'
fdroid_fullImplementation 'org.slf4j:slf4j-simple:1.7.30'
queermotionImplementation 'su.litvak.chromecast:api-v2:0.11.3'
queermotionImplementation 'com.fasterxml.jackson.core:jackson-core:2.12.0'
queermotionImplementation 'org.slf4j:slf4j-simple:1.7.30'
} }

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="app.fedilab.fedilabtube">
<uses-permission android:name="com.android.vending.BILLING" />
<application
android:name=".FedilabTube"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:allowBackup">
<activity
android:name=".PeertubeActivity"
tools:node="mergeOnlyAttributes">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- The app is a good candidate for URL in https://domain.name/videos/watch/xxxxx-->
<data
android:host="*"
android:pathPrefix="/videos/watch/"
android:scheme="https" />
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".PeertubeActivity" />
</activity>
<activity
android:name=".expandedcontrols.ExpandedControlsActivity"
android:theme="@style/AppThemeNoActionBar"
/>
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="app.fedilab.fedilabtube.provider.CastOptionsProvider" />
</application>
</manifest>

View File

@ -0,0 +1,41 @@
package app.fedilab.fedilabtube;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of TubeLab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import app.fedilab.fedilabtube.databinding.ActivityMainBinding;
public class BaseMainActivity extends AppCompatActivity {
protected ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
}
//Method for discovering cast devices
public void discoverCast() {
}
}

View File

@ -0,0 +1,182 @@
package app.fedilab.fedilabtube;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.cast.framework.CastSession;
import com.google.android.gms.cast.framework.SessionManagerListener;
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import com.google.android.gms.common.images.WebImage;
import app.fedilab.fedilabtube.client.data.VideoData;
import app.fedilab.fedilabtube.databinding.ActivityPeertubeBinding;
import app.fedilab.fedilabtube.helper.Helper;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of TubeLab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
public class BasePeertubeActivity extends AppCompatActivity {
protected ActivityPeertubeBinding binding;
protected VideoData.Video peertube;
protected SimpleExoPlayer player;
protected String videoURL;
protected String subtitlesStr;
private CastContext mCastContext;
private CastSession mCastSession;
private SessionManagerListener<CastSession> mSessionManagerListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityPeertubeBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
int search_cast = sharedpreferences.getInt(getString(R.string.set_cast_choice), BuildConfig.cast_enabled);
if (search_cast == 1) {
setupCastListener();
mCastContext = CastContext.getSharedInstance(this);
mCastSession = mCastContext.getSessionManager().getCurrentCastSession();
}
}
protected void loadCast() {
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
movieMetadata.putString(MediaMetadata.KEY_TITLE, peertube.getTitle());
movieMetadata.putString(MediaMetadata.KEY_ARTIST, peertube.getAccount().getDisplayName());
if (subtitlesStr != null) {
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, subtitlesStr);
}
movieMetadata.addImage(new WebImage(Uri.parse("https://" + peertube.getChannel().getHost() + peertube.getPreviewPath())));
MediaInfo mediaInfo = new MediaInfo.Builder(videoURL)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setMetadata(movieMetadata)
.setStreamDuration(peertube.getDuration() * 1000)
.build();
if (mCastSession != null) {
RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
remoteMediaClient.load(mediaInfo);
}
}
private void setupCastListener() {
mSessionManagerListener = new SessionManagerListener<CastSession>() {
@Override
public void onSessionStarting(CastSession castSession) {
}
@Override
public void onSessionStarted(CastSession castSession, String s) {
onApplicationConnected(castSession, true);
}
@Override
public void onSessionStartFailed(CastSession castSession, int i) {
onApplicationDisconnected();
}
@Override
public void onSessionEnding(CastSession castSession) {
onApplicationDisconnected();
}
@Override
public void onSessionEnded(CastSession castSession, int i) {
onApplicationDisconnected();
}
@Override
public void onSessionResuming(CastSession castSession, String s) {
}
@Override
public void onSessionResumed(CastSession castSession, boolean b) {
onApplicationConnected(castSession, false);
}
@Override
public void onSessionResumeFailed(CastSession castSession, int i) {
onApplicationDisconnected();
}
@Override
public void onSessionSuspended(CastSession castSession, int i) {
onApplicationDisconnected();
}
private void onApplicationConnected(CastSession castSession, boolean hide) {
mCastSession = castSession;
supportInvalidateOptionsMenu();
player.setPlayWhenReady(false);
if (hide) {
binding.doubleTapPlayerView.setVisibility(View.INVISIBLE);
}
binding.minController.castMiniController.setVisibility(View.VISIBLE);
loadCast();
}
private void onApplicationDisconnected() {
binding.doubleTapPlayerView.setVisibility(View.VISIBLE);
binding.minController.castMiniController.setVisibility(View.GONE);
supportInvalidateOptionsMenu();
}
};
}
@Override
protected void onResume() {
mCastContext.getSessionManager().addSessionManagerListener(
mSessionManagerListener, CastSession.class);
super.onResume();
}
@Override
protected void onPause() {
mCastContext.getSessionManager().removeSessionManagerListener(
mSessionManagerListener, CastSession.class);
super.onPause();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.video_menu, menu);
CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
menu,
R.id.media_route_button);
return true;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2016 Google LLC. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.fedilab.fedilabtube.expandedcontrols;
import android.view.Menu;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity;
import app.fedilab.fedilabtube.R;
public class ExpandedControlsActivity extends ExpandedControllerActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.video_menu, menu);
CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_button);
return true;
}
}

View File

@ -0,0 +1,54 @@
package app.fedilab.fedilabtube.provider;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of TubeLab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.content.Context;
import com.google.android.gms.cast.framework.CastOptions;
import com.google.android.gms.cast.framework.OptionsProvider;
import com.google.android.gms.cast.framework.SessionProvider;
import com.google.android.gms.cast.framework.media.CastMediaOptions;
import com.google.android.gms.cast.framework.media.NotificationOptions;
import java.util.List;
import app.fedilab.fedilabtube.BuildConfig;
import app.fedilab.fedilabtube.expandedcontrols.ExpandedControlsActivity;
import app.fedilab.fedilabtube.helper.Helper;
public class CastOptionsProvider implements OptionsProvider {
@Override
public CastOptions getCastOptions(Context context) {
NotificationOptions notificationOptions = new NotificationOptions.Builder()
.setTargetActivityClassName(ExpandedControlsActivity.class.getName())
.build();
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName())
.build();
return new CastOptions.Builder()
.setReceiverApplicationId(BuildConfig.FLAVOR.compareTo("bittube") == 0 ? Helper.CAST_ID_BITTUBE : Helper.CAST_ID)
.setCastMediaOptions(mediaOptions)
.build();
}
@Override
public List<SessionProvider> getAdditionalSessionProviders(Context context) {
return null;
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/castMiniController"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:visibility="gone">
<fragment
class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/media_route_button"
android:title="@string/cast"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always" />
</menu>

View File

@ -16,7 +16,7 @@
<activity <activity
android:name=".PeertubeActivity" android:name=".PeertubeActivity"
tools:node="mergeOnlyAttributes"> tools:node="merge">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -28,8 +28,21 @@
android:pathPrefix="/videos/watch/" android:pathPrefix="/videos/watch/"
android:scheme="https" /> android:scheme="https" />
</intent-filter> </intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".PeertubeActivity" />
</activity> </activity>
<activity
android:name=".expandedcontrols.ExpandedControlsActivity"
android:theme="@style/AppThemeNoActionBar"
/>
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="app.fedilab.fedilabtube.provider.CastOptionsProvider" />
<activity <activity
android:name=".DonationActivity" android:name=".DonationActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"

View File

@ -61,7 +61,7 @@ public class AboutActivity extends AppCompatActivity {
content.setSpan(new ForegroundColorSpan(ContextCompat.getColor(AboutActivity.this, Helper.getColorAccent())), 0, content.length(), 0); content.setSpan(new ForegroundColorSpan(ContextCompat.getColor(AboutActivity.this, Helper.getColorAccent())), 0, content.length(), 0);
developer_mastodon.setText(content); developer_mastodon.setText(content);
developer_mastodon.setOnClickListener(v -> { developer_mastodon.setOnClickListener(v -> {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://toot.fedilab.app/@Tubelab")); Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://toot.fedilab.app/@apps"));
startActivity(browserIntent); startActivity(browserIntent);
}); });
@ -80,15 +80,6 @@ public class AboutActivity extends AppCompatActivity {
TextView app_name = findViewById(R.id.app_name); TextView app_name = findViewById(R.id.app_name);
app_name.setText(R.string.app_name); app_name.setText(R.string.app_name);
//Developer Github
TextView github = findViewById(R.id.github);
content = new SpannableString(github.getText().toString());
content.setSpan(new UnderlineSpan(), 0, content.length(), 0);
github.setText(content);
github.setOnClickListener(v -> {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/stom79"));
startActivity(browserIntent);
});
//Developer Framagit //Developer Framagit
TextView framagit = findViewById(R.id.framagit); TextView framagit = findViewById(R.id.framagit);
@ -100,15 +91,6 @@ public class AboutActivity extends AppCompatActivity {
startActivity(browserIntent); startActivity(browserIntent);
}); });
//Developer Codeberg
TextView codeberg = findViewById(R.id.codeberg);
content = new SpannableString(codeberg.getText().toString());
content.setSpan(new UnderlineSpan(), 0, content.length(), 0);
codeberg.setText(content);
codeberg.setOnClickListener(v -> {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://codeberg.org/tom79"));
startActivity(browserIntent);
});
LinearLayout donation_container = findViewById(R.id.donation_container); LinearLayout donation_container = findViewById(R.id.donation_container);
if (BuildConfig.google_restriction || !BuildConfig.full_instances) { if (BuildConfig.google_restriction || !BuildConfig.full_instances) {

View File

@ -65,7 +65,7 @@ public class BaseFedilabTube extends MultiDexApplication {
MultiDex.install(BaseFedilabTube.this); MultiDex.install(BaseFedilabTube.this);
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
int themePref = sharedpreferences.getInt(Helper.SET_THEME, Helper.DEFAULT_MODE); int themePref = sharedpreferences.getInt(Helper.SET_THEME, BuildConfig.default_theme);
ThemeHelper.switchTo(themePref); ThemeHelper.switchTo(themePref);

View File

@ -22,6 +22,7 @@ import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan; import android.text.style.UnderlineSpan;
import android.util.Log;
import android.util.Patterns; import android.util.Patterns;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -37,17 +38,16 @@ import androidx.core.content.ContextCompat;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.List;
import java.util.Iterator;
import java.util.Map;
import app.fedilab.fedilabtube.client.mastodon.RetrofitMastodonAPI;
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI; import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
import app.fedilab.fedilabtube.client.entities.AcadInstances;
import app.fedilab.fedilabtube.client.entities.Error; import app.fedilab.fedilabtube.client.entities.Error;
import app.fedilab.fedilabtube.client.entities.Oauth; import app.fedilab.fedilabtube.client.entities.Oauth;
import app.fedilab.fedilabtube.client.entities.OauthParams; import app.fedilab.fedilabtube.client.entities.OauthParams;
import app.fedilab.fedilabtube.client.entities.Token; import app.fedilab.fedilabtube.client.entities.Token;
import app.fedilab.fedilabtube.client.entities.WellKnownNodeinfo; import app.fedilab.fedilabtube.client.entities.WellKnownNodeinfo;
import app.fedilab.fedilabtube.client.mastodon.RetrofitMastodonAPI;
import app.fedilab.fedilabtube.databinding.ActivityLoginBinding; import app.fedilab.fedilabtube.databinding.ActivityLoginBinding;
import app.fedilab.fedilabtube.helper.Helper; import app.fedilab.fedilabtube.helper.Helper;
import app.fedilab.fedilabtube.helper.HelperAcadInstance; import app.fedilab.fedilabtube.helper.HelperAcadInstance;
@ -120,16 +120,7 @@ public class LoginActivity extends AppCompatActivity {
} }
if (!BuildConfig.full_instances) { if (!BuildConfig.full_instances) {
binding.loginUid.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
if (binding.loginUid.getText() != null && android.util.Patterns.EMAIL_ADDRESS.matcher(binding.loginUid.getText().toString().trim()).matches()) {
String[] emailArray = binding.loginUid.getText().toString().split("@");
if (emailArray.length > 1) {
binding.loginButton.callOnClick();
}
}
}
});
binding.loginUidContainer.setVisibility(View.GONE); binding.loginUidContainer.setVisibility(View.GONE);
binding.loginPasswdContainer.setVisibility(View.GONE); binding.loginPasswdContainer.setVisibility(View.GONE);
binding.loginInstanceContainer.setVisibility(View.GONE); binding.loginInstanceContainer.setVisibility(View.GONE);
@ -137,24 +128,24 @@ public class LoginActivity extends AppCompatActivity {
binding.instancePickerTitle.setVisibility(View.VISIBLE); binding.instancePickerTitle.setVisibility(View.VISIBLE);
binding.instancePicker.setVisibility(View.VISIBLE); binding.instancePicker.setVisibility(View.VISIBLE);
HashMap<String, String> instancesMap = new HashMap<>(HelperAcadInstance.instances_themes);
Iterator<Map.Entry<String, String>> it = instancesMap.entrySet().iterator(); List<AcadInstances> acadInstances = AcadInstances.getInstances();
String[] academiesKey = new String[HelperAcadInstance.instances_themes.size()]; String[] academiesKey = new String[acadInstances.size()];
String[] academiesValue = new String[HelperAcadInstance.instances_themes.size()]; String[] academiesValue = new String[acadInstances.size()];
String acad = HelperInstance.getLiveInstance(LoginActivity.this); String acad = HelperInstance.getLiveInstance(LoginActivity.this);
int position = 0; int position = 0;
int i = 0; int i = 0;
while (it.hasNext()) { for (AcadInstances ac : acadInstances) {
Map.Entry<String, String> pair = it.next(); academiesKey[i] = ac.getName();
academiesKey[i] = pair.getKey(); academiesValue[i] = ac.getUrl();
academiesValue[i] = pair.getValue(); Log.v(Helper.TAG, "url 1: " + ac.getUrl());
if (pair.getValue().compareTo(acad) == 0) { Log.v(Helper.TAG, "url 2: " + acad);
if (ac.getUrl().compareTo(acad) == 0) {
position = i; position = i;
Log.v(Helper.TAG, "ok: " + position);
} }
it.remove();
i++; i++;
} }
binding.instancePicker.setSelection(position, true);
ArrayAdapter<String> adapterChannel = new ArrayAdapter<>(LoginActivity.this, ArrayAdapter<String> adapterChannel = new ArrayAdapter<>(LoginActivity.this,
android.R.layout.simple_spinner_dropdown_item, academiesKey); android.R.layout.simple_spinner_dropdown_item, academiesKey);
binding.instancePicker.setAdapter(adapterChannel); binding.instancePicker.setAdapter(adapterChannel);
@ -162,6 +153,17 @@ public class LoginActivity extends AppCompatActivity {
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
acadInstance = academiesValue[position]; acadInstance = academiesValue[position];
if (acadInstances.get(position).isOpenId()) {
binding.loginUidContainer.setVisibility(View.GONE);
binding.loginPasswdContainer.setVisibility(View.GONE);
binding.loginInstanceContainer.setVisibility(View.GONE);
binding.createAnAccountPeertube.setVisibility(View.GONE);
} else {
binding.loginUidContainer.setVisibility(View.VISIBLE);
binding.loginPasswdContainer.setVisibility(View.VISIBLE);
binding.loginInstanceContainer.setVisibility(View.GONE);
binding.createAnAccountPeertube.setVisibility(View.VISIBLE);
}
} }
@Override @Override
@ -169,6 +171,7 @@ public class LoginActivity extends AppCompatActivity {
} }
}); });
binding.instancePicker.setSelection(position, true);
} }
if (BuildConfig.allow_remote_connections) { if (BuildConfig.allow_remote_connections) {
binding.loginInstance.setOnFocusChangeListener((v, hasFocus) -> { binding.loginInstance.setOnFocusChangeListener((v, hasFocus) -> {
@ -196,7 +199,7 @@ public class LoginActivity extends AppCompatActivity {
} }
binding.loginButton.setOnClickListener(v -> { binding.loginButton.setOnClickListener(v -> {
if (!BuildConfig.full_instances) { if (!BuildConfig.full_instances && AcadInstances.isOpenId(acadInstance)) {
new Thread(() -> { new Thread(() -> {
try { try {
Oauth oauth = new RetrofitPeertubeAPI(LoginActivity.this, acadInstance, null).oauthClient(null, null, null, null); Oauth oauth = new RetrofitPeertubeAPI(LoginActivity.this, acadInstance, null).oauthClient(null, null, null, null);
@ -275,15 +278,13 @@ public class LoginActivity extends AppCompatActivity {
return; return;
} }
String finalInstance = instance; String finalInstance = instance;
if (BuildConfig.full_instances) { new Thread(() -> {
new Thread(() -> { WellKnownNodeinfo.NodeInfo instanceNodeInfo = null;
WellKnownNodeinfo.NodeInfo instanceNodeInfo = null; if (BuildConfig.allow_remote_connections) {
if (BuildConfig.allow_remote_connections) { instanceNodeInfo = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).getNodeInfo();
instanceNodeInfo = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).getNodeInfo(); }
} connectToFediverse(finalInstance, instanceNodeInfo);
connectToFediverse(finalInstance, instanceNodeInfo); }).start();
}).start();
}
} }
}); });
} }

View File

@ -16,17 +16,14 @@ package app.fedilab.fedilabtube;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -37,14 +34,12 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.appcompat.widget.TooltipCompat; import androidx.appcompat.widget.TooltipCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
@ -53,18 +48,9 @@ import com.kobakei.ratethisapp.RateThisApp;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -73,7 +59,7 @@ import java.util.regex.Pattern;
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI; import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
import app.fedilab.fedilabtube.client.data.AccountData.Account; import app.fedilab.fedilabtube.client.data.AccountData.Account;
import app.fedilab.fedilabtube.client.data.InstanceData; import app.fedilab.fedilabtube.client.data.InstanceData;
import app.fedilab.fedilabtube.client.data.VideoData; import app.fedilab.fedilabtube.client.entities.AcadInstances;
import app.fedilab.fedilabtube.client.entities.Error; import app.fedilab.fedilabtube.client.entities.Error;
import app.fedilab.fedilabtube.client.entities.OauthParams; import app.fedilab.fedilabtube.client.entities.OauthParams;
import app.fedilab.fedilabtube.client.entities.PeertubeInformation; import app.fedilab.fedilabtube.client.entities.PeertubeInformation;
@ -95,17 +81,13 @@ import app.fedilab.fedilabtube.sqlite.Sqlite;
import app.fedilab.fedilabtube.sqlite.StoredInstanceDAO; import app.fedilab.fedilabtube.sqlite.StoredInstanceDAO;
import app.fedilab.fedilabtube.viewmodel.TimelineVM; import app.fedilab.fedilabtube.viewmodel.TimelineVM;
import es.dmoral.toasty.Toasty; import es.dmoral.toasty.Toasty;
import su.litvak.chromecast.api.v2.ChromeCast;
import su.litvak.chromecast.api.v2.ChromeCasts;
import su.litvak.chromecast.api.v2.ChromeCastsListener;
import su.litvak.chromecast.api.v2.MediaStatus;
import static app.fedilab.fedilabtube.MainActivity.TypeOfConnection.NORMAL; import static app.fedilab.fedilabtube.MainActivity.TypeOfConnection.NORMAL;
import static app.fedilab.fedilabtube.MainActivity.TypeOfConnection.SURFING; import static app.fedilab.fedilabtube.MainActivity.TypeOfConnection.SURFING;
import static app.fedilab.fedilabtube.helper.Helper.peertubeInformation; import static app.fedilab.fedilabtube.helper.Helper.peertubeInformation;
public class MainActivity extends AppCompatActivity implements ChromeCastsListener { public class MainActivity extends BaseMainActivity {
public static int PICK_INSTANCE = 5641; public static int PICK_INSTANCE = 5641;
@ -113,9 +95,8 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
public static UserMe userMe; public static UserMe userMe;
public static InstanceData.InstanceConfig instanceConfig; public static InstanceData.InstanceConfig instanceConfig;
public static TypeOfConnection typeOfConnection; public static TypeOfConnection typeOfConnection;
public static List<ChromeCast> chromeCasts;
public static ChromeCast chromeCast;
public static boolean chromecastActivated = false;
private DisplayVideosFragment recentFragment, locaFragment, trendingFragment, subscriptionFragment, mostLikedFragment; private DisplayVideosFragment recentFragment, locaFragment, trendingFragment, subscriptionFragment, mostLikedFragment;
private DisplayOverviewFragment overviewFragment; private DisplayOverviewFragment overviewFragment;
private ActivityMainBinding binding; private ActivityMainBinding binding;
@ -158,8 +139,7 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
} }
return true; return true;
}; };
private BroadcastReceiver manage_chromecast;
private VideoData.Video castedTube;
@SuppressLint("ApplySharedPref") @SuppressLint("ApplySharedPref")
public static void showRadioButtonDialogFullInstances(Activity activity, boolean storeInDb) { public static void showRadioButtonDialogFullInstances(Activity activity, boolean storeInDb) {
@ -234,123 +214,20 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
} }
} }
@Override
public void newChromeCastDiscovered(ChromeCast chromeCast) {
if (chromeCasts == null) {
chromeCasts = new ArrayList<>();
chromeCasts.add(chromeCast);
} else {
boolean canBeAdded = true;
for (ChromeCast cast : chromeCasts) {
if (cast.getName().compareTo(chromeCast.getName()) == 0) {
canBeAdded = false;
break;
}
}
if (canBeAdded) {
chromeCasts.add(chromeCast);
}
}
try {
if (chromeCast.isAppRunning(Helper.CAST_ID) && chromeCast.getMediaStatus() != null && chromeCast.getMediaStatus().playerState != null) {
if (binding.castInfo.getVisibility() == View.GONE) {
binding.castInfo.setVisibility(View.VISIBLE);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void chromeCastRemoved(ChromeCast chromeCast) {
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
binding = null; binding = null;
ChromeCasts.unregisterListener(this);
if (manage_chromecast != null) {
LocalBroadcastManager.getInstance(MainActivity.this).unregisterReceiver(manage_chromecast);
new Thread(() -> {
if (chromeCasts != null && chromeCasts.size() > 0) {
for (ChromeCast cast : chromeCasts) {
try {
cast.stopApp();
cast.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
if (chromeCasts != null) {
chromeCasts = null;
}
if (chromeCast != null) {
chromeCast = null;
}
} }
//Method for discovering cast devices
public void discoverCast() {
new Thread(() -> {
if (chromeCasts != null) {
for (ChromeCast cast : chromeCasts) {
try {
cast.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
chromeCasts = null;
}
chromeCasts = new ArrayList<>();
try {
List<NetworkInterface> interfaces;
interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface ni : interfaces) {
if ((!ni.isLoopback()) && ni.isUp() && (ni.getName().equals("wlan0"))) {
Enumeration<InetAddress> inetAddressEnumeration = ni.getInetAddresses();
while (inetAddressEnumeration.hasMoreElements()) {
InetAddress inetAddress = inetAddressEnumeration.nextElement();
ChromeCasts.restartDiscovery(inetAddress);
int tryFind = 0;
while (ChromeCasts.get().isEmpty() && tryFind < 5) {
try {
//noinspection BusyWait
Thread.sleep(1000);
tryFind++;
} catch (InterruptedException ignored) {
}
}
}
}
}
ChromeCasts.stopDiscovery();
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = this::invalidateOptionsMenu;
mainHandler.post(myRunnable);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater()); binding = super.binding;
View view = binding.getRoot();
setContentView(view);
ChromeCastsListener chromeCastsListener = this;
ChromeCasts.registerListener(chromeCastsListener);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
@ -462,92 +339,10 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
} }
binding.castClose.setOnClickListener(v -> {
Intent intentBC = new Intent(Helper.RECEIVE_CAST_SETTINGS);
Bundle b = new Bundle();
b.putInt("displayed", 0);
intentBC.putExtras(b);
LocalBroadcastManager.getInstance(MainActivity.this).sendBroadcast(intentBC);
});
binding.castTogglePlay.setOnClickListener(v -> {
if (chromeCast != null) {
new Thread(() -> {
try {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> binding.castTogglePlay.setVisibility(View.GONE);
mainHandler.post(myRunnable);
int icon = -1;
if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PLAYING) {
chromeCast.pause();
icon = R.drawable.ic_baseline_play_arrow_32;
} else if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PAUSED) {
chromeCast.play();
icon = R.drawable.ic_baseline_pause_32;
}
if (icon != -1) {
int finalIcon = icon;
myRunnable = () -> binding.castTogglePlay.setImageResource(finalIcon);
mainHandler.post(myRunnable);
}
myRunnable = () -> binding.castTogglePlay.setVisibility(View.VISIBLE);
mainHandler.post(myRunnable);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
});
manage_chromecast = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Bundle b = intent.getExtras();
assert b != null;
int state = b.getInt("state_asked", -1);
int displayed = b.getInt("displayed", -1);
castedTube = b.getParcelable("castedTube");
if (state == 1) {
discoverCast();
} else if (state == 0) {
new Thread(() -> {
try {
if (chromeCast != null) {
chromeCast.stopApp();
chromeCast.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
if (displayed == 1) {
chromecastActivated = true;
if (castedTube != null) {
binding.castInfo.setVisibility(View.VISIBLE);
Helper.loadGiF(MainActivity.this, castedTube.getThumbnailPath(), binding.castView);
binding.castTitle.setText(castedTube.getTitle());
binding.castDescription.setText(castedTube.getDescription());
}
} else if (displayed == 0) {
chromecastActivated = false;
binding.castInfo.setVisibility(View.GONE);
new Thread(() -> {
try {
if (chromeCast != null) {
chromeCast.stopApp();
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
};
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
LocalBroadcastManager.getInstance(MainActivity.this).registerReceiver(manage_chromecast, new IntentFilter(Helper.RECEIVE_CAST_SETTINGS));
int search_cast = sharedpreferences.getInt(getString(R.string.set_cast_choice), 0); int search_cast = sharedpreferences.getInt(getString(R.string.set_cast_choice), BuildConfig.cast_enabled);
if (search_cast == 1) { if (search_cast == 1) {
discoverCast(); discoverCast();
} }
@ -699,9 +494,9 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
MenuItem settingsItem = menu.findItem(R.id.action_settings); MenuItem settingsItem = menu.findItem(R.id.action_settings);
MenuItem sepiaSearchItem = menu.findItem(R.id.action_sepia_search); MenuItem sepiaSearchItem = menu.findItem(R.id.action_sepia_search);
MenuItem incognitoItem = menu.findItem(R.id.action_incognito); MenuItem incognitoItem = menu.findItem(R.id.action_incognito);
MenuItem instanceItem = menu.findItem(R.id.action_change_instance);
MenuItem accountItem = menu.findItem(R.id.action_account); MenuItem accountItem = menu.findItem(R.id.action_account);
MenuItem donateItem = menu.findItem(R.id.action_donate); MenuItem donateItem = menu.findItem(R.id.action_donate);
MenuItem changeInstanceItem = menu.findItem(R.id.action_change_instance);
FrameLayout rootView = (FrameLayout) accountItem.getActionView(); FrameLayout rootView = (FrameLayout) accountItem.getActionView();
@ -715,24 +510,15 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
redCircle.setVisibility(View.GONE); redCircle.setVisibility(View.GONE);
} }
TooltipCompat.setTooltipText(accountItem.getActionView(), getText(R.string.account)); TooltipCompat.setTooltipText(accountItem.getActionView(), getText(R.string.account));
if (BuildConfig.full_instances && BuildConfig.google_restriction) { if (BuildConfig.FLAVOR.compareTo("google_full") == 0) {
donateItem.setVisible(true); donateItem.setVisible(true);
} }
if (BuildConfig.surfing_mode && ((Helper.isLoggedIn(MainActivity.this) && typeOfConnection == NORMAL) || typeOfConnection == SURFING)) { if (!BuildConfig.instance_switcher) {
binding.instances.setVisibility(View.VISIBLE); changeInstanceItem.setVisible(false);
binding.instances.setOnClickListener(null);
binding.instances.setOnClickListener(v -> {
Intent intent = new Intent(MainActivity.this, ManageInstancesActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.slide_in_up, R.anim.slide_out_up);
});
} else {
binding.instances.setVisibility(View.GONE);
} }
switch (typeOfConnection) { switch (typeOfConnection) {
case UNKNOWN: case UNKNOWN:
instanceItem.setVisible(false);
accountItem.setVisible(false); accountItem.setVisible(false);
uploadItem.setVisible(false); uploadItem.setVisible(false);
myVideosItem.setVisible(false); myVideosItem.setVisible(false);
@ -746,7 +532,9 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
case NORMAL: case NORMAL:
accountItem.setVisible(true); accountItem.setVisible(true);
if (Helper.isLoggedIn(MainActivity.this)) { if (Helper.isLoggedIn(MainActivity.this)) {
instanceItem.setVisible(false); if (!BuildConfig.full_instances) {
changeInstanceItem.setVisible(false);
}
uploadItem.setVisible(true); uploadItem.setVisible(true);
myVideosItem.setVisible(true); myVideosItem.setVisible(true);
playslistItem.setVisible(true); playslistItem.setVisible(true);
@ -758,7 +546,6 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
boolean checked = sharedpreferences.getBoolean(getString(R.string.set_store_in_history), true); boolean checked = sharedpreferences.getBoolean(getString(R.string.set_store_in_history), true);
incognitoItem.setChecked(checked); incognitoItem.setChecked(checked);
} else { } else {
instanceItem.setVisible(true);
uploadItem.setVisible(false); uploadItem.setVisible(false);
myVideosItem.setVisible(false); myVideosItem.setVisible(false);
playslistItem.setVisible(!BuildConfig.full_instances); playslistItem.setVisible(!BuildConfig.full_instances);
@ -769,7 +556,6 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
} }
break; break;
case SURFING: case SURFING:
instanceItem.setVisible(false);
accountItem.setVisible(true); accountItem.setVisible(true);
uploadItem.setVisible(false); uploadItem.setVisible(false);
myVideosItem.setVisible(false); myVideosItem.setVisible(false);
@ -781,9 +567,6 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
break; break;
} }
if (!BuildConfig.instance_switcher) {
instanceItem.setVisible(false);
}
if (!BuildConfig.sepia_search) { if (!BuildConfig.sepia_search) {
sepiaSearchItem.setVisible(false); sepiaSearchItem.setVisible(false);
@ -827,7 +610,9 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
String action = "TIMELINE"; String action = "TIMELINE";
if (item.getItemId() == R.id.action_change_instance) { if (item.getItemId() == R.id.action_change_instance) {
if (BuildConfig.full_instances) { if (BuildConfig.full_instances) {
showRadioButtonDialogFullInstances(MainActivity.this, false); Intent intent = new Intent(MainActivity.this, ManageInstancesActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.slide_in_up, R.anim.slide_out_up);
} else { } else {
showRadioButtonDialog(); showRadioButtonDialog();
} }
@ -941,21 +726,19 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String acad = HelperInstance.getLiveInstance(MainActivity.this); String acad = HelperInstance.getLiveInstance(MainActivity.this);
int i = 0; int i = 0;
HashMap<String, String> instancesMap = new HashMap<>(HelperAcadInstance.instances_themes); List<AcadInstances> acadInstances = AcadInstances.getInstances();
Iterator<Map.Entry<String, String>> it = instancesMap.entrySet().iterator(); String[] academiesKey = new String[acadInstances.size()];
String[] academiesKey = new String[HelperAcadInstance.instances_themes.size()]; String[] academiesValue = new String[acadInstances.size()];
String[] academiesValue = new String[HelperAcadInstance.instances_themes.size()];
int position = 0; int position = 0;
while (it.hasNext()) { for (AcadInstances ac : acadInstances) {
Map.Entry<String, String> pair = it.next(); academiesKey[i] = ac.getName();
academiesKey[i] = pair.getKey(); academiesValue[i] = ac.getUrl();
academiesValue[i] = pair.getValue(); if (ac.getUrl().compareTo(acad) == 0) {
if (pair.getValue().compareTo(acad) == 0) {
position = i; position = i;
} }
it.remove();
i++; i++;
} }
alt_bld.setSingleChoiceItems(academiesKey, position, (dialog, item) -> { alt_bld.setSingleChoiceItems(academiesKey, position, (dialog, item) -> {
String newInstance = academiesValue[item]; String newInstance = academiesValue[item];
SharedPreferences.Editor editor = sharedpreferences.edit(); SharedPreferences.Editor editor = sharedpreferences.edit();

View File

@ -44,7 +44,6 @@ import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan; import android.text.style.ClickableSpan;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
@ -52,7 +51,6 @@ import android.view.WindowManager;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.view.animation.TranslateAnimation; import android.view.animation.TranslateAnimation;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.webkit.MimeTypeMap;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
@ -63,14 +61,12 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.PopupMenu;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.graphics.drawable.DrawableCompat;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -111,8 +107,6 @@ import com.google.android.material.snackbar.Snackbar;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -163,19 +157,12 @@ import app.fedilab.fedilabtube.webview.CustomWebview;
import app.fedilab.fedilabtube.webview.MastalabWebChromeClient; import app.fedilab.fedilabtube.webview.MastalabWebChromeClient;
import app.fedilab.fedilabtube.webview.MastalabWebViewClient; import app.fedilab.fedilabtube.webview.MastalabWebViewClient;
import es.dmoral.toasty.Toasty; import es.dmoral.toasty.Toasty;
import su.litvak.chromecast.api.v2.ChromeCast;
import su.litvak.chromecast.api.v2.MediaStatus;
import su.litvak.chromecast.api.v2.Status;
import static app.fedilab.fedilabtube.MainActivity.chromeCast;
import static app.fedilab.fedilabtube.MainActivity.chromeCasts;
import static app.fedilab.fedilabtube.MainActivity.chromecastActivated;
import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.ADD_COMMENT; import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.ADD_COMMENT;
import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.RATEVIDEO; import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.RATEVIDEO;
import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.REPLY; import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.REPLY;
import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.REPORT_ACCOUNT; import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.REPORT_ACCOUNT;
import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.REPORT_VIDEO; import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.REPORT_VIDEO;
import static app.fedilab.fedilabtube.helper.Helper.CAST_ID;
import static app.fedilab.fedilabtube.helper.Helper.canMakeAction; import static app.fedilab.fedilabtube.helper.Helper.canMakeAction;
import static app.fedilab.fedilabtube.helper.Helper.getAttColor; import static app.fedilab.fedilabtube.helper.Helper.getAttColor;
import static app.fedilab.fedilabtube.helper.Helper.isLoggedIn; import static app.fedilab.fedilabtube.helper.Helper.isLoggedIn;
@ -184,15 +171,13 @@ import static app.fedilab.fedilabtube.helper.Helper.peertubeInformation;
import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO; import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO;
public class PeertubeActivity extends AppCompatActivity implements CommentListAdapter.AllCommentRemoved, Player.EventListener, VideoListener, TorrentListener, MenuAdapter.ItemClicked, MenuItemAdapter.ItemAction { public class PeertubeActivity extends BasePeertubeActivity implements CommentListAdapter.AllCommentRemoved, Player.EventListener, VideoListener, TorrentListener, MenuAdapter.ItemClicked, MenuItemAdapter.ItemAction {
public static String video_id; public static String video_id;
public static List<String> playedVideos = new ArrayList<>(); public static List<String> playedVideos = new ArrayList<>();
private String peertubeInstance, videoUuid; private String peertubeInstance, videoUuid;
private ImageView fullScreenIcon; private ImageView fullScreenIcon;
private SimpleExoPlayer player;
private boolean fullScreenMode; private boolean fullScreenMode;
private VideoData.Video peertube;
private int mode; private int mode;
private Map<String, List<PlaylistExist>> playlists; private Map<String, List<PlaylistExist>> playlists;
private boolean playInMinimized, autoPlay, autoFullscreen; private boolean playInMinimized, autoPlay, autoFullscreen;
@ -218,7 +203,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
private String currentCaption; private String currentCaption;
private boolean isRemote; private boolean isRemote;
private boolean willPlayFromIntent; private boolean willPlayFromIntent;
private String chromeCastVideoURL;
private app.fedilab.fedilabtube.client.mastodon.Status status; private app.fedilab.fedilabtube.client.mastodon.Status status;
Uri captionURI; Uri captionURI;
String captionLang; String captionLang;
@ -307,10 +292,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
binding = super.binding;
binding = ActivityPeertubeBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
videoOrientationType = videoOrientation.LANDSCAPE; videoOrientationType = videoOrientation.LANDSCAPE;
max_id = "0"; max_id = "0";
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
@ -512,34 +494,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
} }
}); });
binding.castPlay.setOnClickListener(v -> {
binding.castLoader.setVisibility(View.VISIBLE);
if (chromeCast != null) {
new Thread(() -> {
try {
int icon = -1;
if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PLAYING) {
chromeCast.pause();
icon = R.drawable.ic_baseline_play_arrow_32;
} else if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PAUSED) {
chromeCast.play();
icon = R.drawable.ic_baseline_pause_32;
}
if (icon != -1) {
Handler mainHandler = new Handler(Looper.getMainLooper());
int finalIcon = icon;
Runnable myRunnable = () -> binding.castPlay.setImageResource(finalIcon);
mainHandler.post(myRunnable);
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> binding.castLoader.setVisibility(View.GONE);
mainHandler.post(myRunnable);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
});
} }
@ -798,20 +753,6 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
}).start(); }).start();
} }
@Override
public boolean onCreateOptionsMenu(@NotNull Menu menu) {
getMenuInflater().inflate(R.menu.video_menu, menu);
MenuItem castItem = menu.findItem(R.id.action_cast);
if (chromeCasts != null && chromeCasts.size() > 0) {
castItem.setVisible(true);
if (chromeCast != null && chromeCast.isConnected()) {
castItem.setIcon(R.drawable.ic_baseline_cast_connected_24);
} else {
castItem.setIcon(R.drawable.ic_baseline_cast_24);
}
}
return true;
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
@ -821,101 +762,6 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
} }
finish(); finish();
return true; return true;
} else if (item.getItemId() == R.id.action_cast) {
if (chromeCasts != null && chromeCasts.size() > 0) {
String[] chromecast_choice = new String[chromeCasts.size()];
AlertDialog.Builder alt_bld = new AlertDialog.Builder(this);
alt_bld.setTitle(R.string.chromecast_choice);
int i = 0;
for (ChromeCast cc : chromeCasts) {
chromecast_choice[i] = cc.getTitle();
i++;
}
i = 0;
for (ChromeCast cc : chromeCasts) {
if (chromecastActivated && cc.isConnected()) {
break;
}
i++;
}
alt_bld.setSingleChoiceItems(chromecast_choice, i, (dialog, position) -> {
chromeCast = chromeCasts.get(position);
new Thread(() -> {
if (chromeCast != null) {
Intent intentBC = new Intent(Helper.RECEIVE_CAST_SETTINGS);
Bundle b = new Bundle();
if (chromecastActivated) {
b.putInt("displayed", 0);
intentBC.putExtras(b);
LocalBroadcastManager.getInstance(PeertubeActivity.this).sendBroadcast(intentBC);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
binding.doubleTapPlayerView.setVisibility(View.VISIBLE);
binding.castController.setVisibility(View.GONE);
};
mainHandler.post(myRunnable);
} else {
b.putInt("displayed", 1);
b.putParcelable("castedTube", peertube);
intentBC.putExtras(b);
LocalBroadcastManager.getInstance(PeertubeActivity.this).sendBroadcast(intentBC);
try {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
invalidateOptionsMenu();
binding.castLoader.setVisibility(View.VISIBLE);
player.setPlayWhenReady(false);
binding.doubleTapPlayerView.setVisibility(View.GONE);
binding.castController.setVisibility(View.VISIBLE);
dialog.dismiss();
if (chromeCastVideoURL != null) {
if (player != null && player.getCurrentPosition() > 0) {
chromeCastVideoURL += "?start=" + (player.getCurrentPosition() / 1000);
}
}
};
mainHandler.post(myRunnable);
if (!chromeCast.isConnected()) {
chromeCast.connect();
}
myRunnable = this::invalidateOptionsMenu;
mainHandler.post(myRunnable);
Status status = chromeCast.getStatus();
if (chromeCast.isAppAvailable(CAST_ID) && !status.isAppRunning(CAST_ID)) {
chromeCast.launchApp(CAST_ID);
}
if (chromeCastVideoURL != null) {
String mime = MimeTypeMap.getFileExtensionFromUrl(chromeCastVideoURL);
chromeCast.setRequestTimeout(60000);
chromeCast.load(peertube.getTitle(), null, chromeCastVideoURL, mime);
chromeCast.play();
binding.castPlay.setImageResource(R.drawable.ic_baseline_pause_32);
}
myRunnable = () -> binding.castLoader.setVisibility(View.GONE);
mainHandler.post(myRunnable);
} catch (IOException | GeneralSecurityException e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
invalidateOptionsMenu();
dialog.dismiss();
};
mainHandler.post(myRunnable);
}
}).start();
});
alt_bld.setPositiveButton(R.string.close, (dialog, id) -> dialog.dismiss());
AlertDialog alert = alt_bld.create();
alert.show();
}
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@ -1417,6 +1263,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
}); });
} }
/** /**
* Manage video to play with different factors * Manage video to play with different factors
* *
@ -1429,7 +1276,10 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
* @param lang String ("en","fr", etc.) * @param lang String ("en","fr", etc.)
*/ */
private void stream(VideoData.Video video, String localTorrentUrl, String resolution, boolean autoPlay, long position, Uri subtitles, String lang) { private void stream(VideoData.Video video, String localTorrentUrl, String resolution, boolean autoPlay, long position, Uri subtitles, String lang) {
String videoURL = localTorrentUrl == null ? video.getFileUrl(resolution, PeertubeActivity.this) : localTorrentUrl; videoURL = localTorrentUrl == null ? video.getFileUrl(resolution, PeertubeActivity.this) : localTorrentUrl;
if (subtitles != null) {
subtitlesStr = subtitles.toString();
}
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
int video_cache = sharedpreferences.getInt(Helper.SET_VIDEO_CACHE, Helper.DEFAULT_VIDEO_CACHE_MB); int video_cache = sharedpreferences.getInt(Helper.SET_VIDEO_CACHE, Helper.DEFAULT_VIDEO_CACHE_MB);
if (videoURL != null && (videoURL.endsWith(".torrent") || videoURL.startsWith("magnet"))) { if (videoURL != null && (videoURL.endsWith(".torrent") || videoURL.startsWith("magnet"))) {
@ -1503,6 +1353,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
if (autoPlay) { if (autoPlay) {
binding.doubleTapPlayerView.hideController(); binding.doubleTapPlayerView.hideController();
} }
// loadCast(video, videoURL, subtitles!=null?subtitles.toString():null);
} }
private void fetchComments() { private void fetchComments() {
@ -1544,7 +1395,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
snackbar.show(); snackbar.show();
return; return;
} }
chromeCastVideoURL = videoURL;
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
String nsfwAction = sharedpreferences.getString(getString(R.string.set_video_sensitive_choice), Helper.BLUR); String nsfwAction = sharedpreferences.getString(getString(R.string.set_video_sensitive_choice), Helper.BLUR);
if (promptNSFW && peertube != null && peertube.isNsfw() && (nsfwAction.compareTo(Helper.BLUR) == 0 || nsfwAction.compareTo(Helper.DO_NOT_LIST) == 0)) { if (promptNSFW && peertube != null && peertube.isNsfw() && (nsfwAction.compareTo(Helper.BLUR) == 0 || nsfwAction.compareTo(Helper.DO_NOT_LIST) == 0)) {
@ -1574,7 +1425,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
@Override @Override
public void onConfigurationChanged(@NotNull Configuration newConfig) { public void onConfigurationChanged(@NotNull Configuration newConfig) {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
if (binding.castController.getVisibility() == View.VISIBLE) { if (binding.minController.castMiniController.getVisibility() == View.VISIBLE) {
return; return;
} }
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
@ -1627,7 +1478,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
onStopCalled = false; onStopCalled = false;
if (player != null && !player.isPlaying()) { if (player != null && !player.isPlaying() && binding.minController.castMiniController.getVisibility() != View.VISIBLE) {
player.setPlayWhenReady(autoPlay); player.setPlayWhenReady(autoPlay);
if (autoPlay) { if (autoPlay) {
binding.doubleTapPlayerView.hideController(); binding.doubleTapPlayerView.hideController();
@ -1643,7 +1494,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
} }
if (player != null && (!isPlayInMinimized || !playInMinimized)) { if (player != null && (!isPlayInMinimized || !playInMinimized)) {
player.setPlayWhenReady(false); player.setPlayWhenReady(false);
} else if (playInMinimized && binding.castController.getVisibility() != View.VISIBLE) { } else if (playInMinimized && binding.minController.castMiniController.getVisibility() != View.VISIBLE) {
enterVideoMode(); enterVideoMode();
} }
} }

View File

@ -43,6 +43,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import app.fedilab.fedilabtube.BuildConfig; import app.fedilab.fedilabtube.BuildConfig;
import app.fedilab.fedilabtube.MainActivity; import app.fedilab.fedilabtube.MainActivity;
@ -85,6 +86,7 @@ import app.fedilab.fedilabtube.viewmodel.PlaylistsVM;
import app.fedilab.fedilabtube.viewmodel.TimelineVM; import app.fedilab.fedilabtube.viewmodel.TimelineVM;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.MultipartBody; import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
import retrofit2.Call; import retrofit2.Call;
@ -103,6 +105,13 @@ public class RetrofitPeertubeAPI {
private String token; private String token;
private Set<String> selection; private Set<String> selection;
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.build();
public RetrofitPeertubeAPI(Context context) { public RetrofitPeertubeAPI(Context context) {
_context = context; _context = context;
instance = HelperInstance.getLiveInstance(context); instance = HelperInstance.getLiveInstance(context);
@ -199,6 +208,7 @@ public class RetrofitPeertubeAPI {
Retrofit retrofit = new Retrofit.Builder() Retrofit retrofit = new Retrofit.Builder()
.baseUrl(finalUrl) .baseUrl(finalUrl)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build(); .build();
SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
if (token == null) { if (token == null) {
@ -215,6 +225,7 @@ public class RetrofitPeertubeAPI {
Retrofit retrofit = new Retrofit.Builder() Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance) .baseUrl("https://" + instance)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build(); .build();
return retrofit.create(PeertubeService.class); return retrofit.create(PeertubeService.class);
} }

View File

@ -0,0 +1,109 @@
package app.fedilab.fedilabtube.client.entities;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of TubeLab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import app.fedilab.fedilabtube.helper.HelperAcadInstance;
public class AcadInstances {
private String name;
private String url;
private boolean openId;
public static boolean isOpenId(String domain) {
List<AcadInstances> instances = getInstances();
for (AcadInstances acadInstance : instances) {
if (acadInstance.getUrl().compareTo(domain) == 0) {
return acadInstance.isOpenId();
}
}
return false;
}
public static List<AcadInstances> getInstances() {
List<AcadInstances> acadInstances = new ArrayList<>();
for (String academie : HelperAcadInstance.academies) {
AcadInstances acadInstance = new AcadInstances();
acadInstance.name = academie;
acadInstance.openId = !Arrays.asList(HelperAcadInstance.notOpenId).contains(academie);
acadInstance.url = getPeertubeUrl(academie);
acadInstances.add(acadInstance);
}
HashMap<String, String> instancesMap = new HashMap<>(HelperAcadInstance.instances_themes);
Iterator<Map.Entry<String, String>> it = instancesMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> pair = it.next();
AcadInstances acadInstance = new AcadInstances();
acadInstance.name = pair.getKey();
acadInstance.openId = true;
acadInstance.url = pair.getValue();
acadInstances.add(acadInstance);
it.remove();
}
return acadInstances;
}
/**
* Returns the peertube URL depending of the academic domain name
*
* @param acad String academic domain name
* @return String the peertube URL
*/
private static String getPeertubeUrl(String acad) {
if (acad.compareTo("education.gouv.fr") == 0 || acad.compareTo("igesr.gouv.fr") == 0) {
acad = "education.fr";
} else if (acad.compareTo("ac-nancy-metz.fr") == 0) {
acad = "ac-nancy.fr";
} else if (acad.compareTo("clermont.fr") == 0) {
acad = "clermont-ferrand.fr";
} else if (acad.compareTo("ac-wf.wf") == 0 || acad.compareTo("ac-mayotte.fr") == 0 || acad.compareTo("ac-noumea.nc") == 0
|| acad.compareTo("ac-guadeloupe.fr") == 0 || acad.compareTo("monvr.pf") == 0 || acad.compareTo("ac-reunion.fr") == 0 ||
acad.compareTo("ac-martinique.fr") == 0 || acad.compareTo("ac-guyane.fr") == 0
) {
acad = "outremer.fr";
}
if (!acad.contains("ac-lyon.fr")) {
//TODO: remove hack for test with openid
/*if( acad.contains("orleans-tours.fr")) {
return "tube-normandie.beta.education.fr";
}*/
return "tube-" + acad.replaceAll("ac-|\\.fr", "") + ".beta.education.fr";
} else {
return "tube.ac-lyon.fr";
}
}
public String getName() {
return name;
}
public String getUrl() {
return url;
}
public boolean isOpenId() {
return openId;
}
}

View File

@ -25,6 +25,7 @@ import android.os.Looper;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.util.concurrent.TimeUnit;
import app.fedilab.fedilabtube.MainActivity; import app.fedilab.fedilabtube.MainActivity;
import app.fedilab.fedilabtube.R; import app.fedilab.fedilabtube.R;
@ -36,6 +37,7 @@ import app.fedilab.fedilabtube.client.entities.Token;
import app.fedilab.fedilabtube.helper.Helper; import app.fedilab.fedilabtube.helper.Helper;
import app.fedilab.fedilabtube.sqlite.MastodonAccountDAO; import app.fedilab.fedilabtube.sqlite.MastodonAccountDAO;
import app.fedilab.fedilabtube.sqlite.Sqlite; import app.fedilab.fedilabtube.sqlite.Sqlite;
import okhttp3.OkHttpClient;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Response; import retrofit2.Response;
import retrofit2.Retrofit; import retrofit2.Retrofit;
@ -49,6 +51,12 @@ public class RetrofitMastodonAPI {
private String instance; private String instance;
private String token; private String token;
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.build();
public Status search(String url) throws Error { public Status search(String url) throws Error {
MastodonService mastodonService2 = init2(); MastodonService mastodonService2 = init2();
Call<Results> statusCall = mastodonService2.searchMessage(getToken(), url); Call<Results> statusCall = mastodonService2.searchMessage(getToken(), url);
@ -147,6 +155,7 @@ public class RetrofitMastodonAPI {
Retrofit retrofit = new Retrofit.Builder() Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance) .baseUrl("https://" + instance)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build(); .build();
SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
if (token == null) { if (token == null) {
@ -159,6 +168,7 @@ public class RetrofitMastodonAPI {
Retrofit retrofit = new Retrofit.Builder() Retrofit retrofit = new Retrofit.Builder()
.baseUrl(finalUrl) .baseUrl(finalUrl)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build(); .build();
SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
if (token == null) { if (token == null) {
@ -171,6 +181,7 @@ public class RetrofitMastodonAPI {
Retrofit retrofit = new Retrofit.Builder() Retrofit retrofit = new Retrofit.Builder()
.baseUrl(finalUrl2) .baseUrl(finalUrl2)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build(); .build();
SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
if (token == null) { if (token == null) {

View File

@ -259,11 +259,9 @@ public class DisplayVideosFragment extends Fragment implements AccountsHorizonta
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
if (binding.swipeContainer != null) { binding.swipeContainer.setEnabled(false);
binding.swipeContainer.setEnabled(false); binding.swipeContainer.setRefreshing(false);
binding.swipeContainer.setRefreshing(false); binding.swipeContainer.clearAnimation();
binding.swipeContainer.clearAnimation();
}
if (getActivity() != null) { if (getActivity() != null) {
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null && getView() != null) { if (imm != null && getView() != null) {
@ -431,7 +429,7 @@ public class DisplayVideosFragment extends Fragment implements AccountsHorizonta
} }
public void manageVIewRelationship(APIResponse apiResponse) { public void manageVIewRelationship(APIResponse apiResponse) {
if (apiResponse.getError() != null) { if (apiResponse.getError() != null || apiResponse.getRelationships() == null) {
return; return;
} }
if (relationship == null) { if (relationship == null) {

View File

@ -34,6 +34,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import app.fedilab.fedilabtube.BuildConfig;
import app.fedilab.fedilabtube.MainActivity; import app.fedilab.fedilabtube.MainActivity;
import app.fedilab.fedilabtube.R; import app.fedilab.fedilabtube.R;
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI; import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
@ -315,7 +316,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared
CharSequence[] entriesTheme = arrayTheme.toArray(new CharSequence[0]); CharSequence[] entriesTheme = arrayTheme.toArray(new CharSequence[0]);
CharSequence[] entryValuesTheme = new CharSequence[3]; CharSequence[] entryValuesTheme = new CharSequence[3];
final SharedPreferences sharedpref = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); final SharedPreferences sharedpref = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
int currentTheme = sharedpref.getInt(Helper.SET_THEME, Helper.DEFAULT_MODE); int currentTheme = sharedpref.getInt(Helper.SET_THEME, BuildConfig.default_theme);
entryValuesTheme[0] = String.valueOf(Helper.LIGHT_MODE); entryValuesTheme[0] = String.valueOf(Helper.LIGHT_MODE);
entryValuesTheme[1] = String.valueOf(Helper.DARK_MODE); entryValuesTheme[1] = String.valueOf(Helper.DARK_MODE);
entryValuesTheme[2] = String.valueOf(Helper.DEFAULT_MODE); entryValuesTheme[2] = String.valueOf(Helper.DEFAULT_MODE);
@ -407,7 +408,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Shared
set_video_in_list_choice.setChecked(videosInList); set_video_in_list_choice.setChecked(videosInList);
//****** Allow Chromecast ******* //****** Allow Chromecast *******
int cast = sharedpref.getInt(getString(R.string.set_cast_choice), 0); int cast = sharedpref.getInt(getString(R.string.set_cast_choice), BuildConfig.cast_enabled);
SwitchPreference set_cast_choice = findPreference(getString(R.string.set_cast_choice)); SwitchPreference set_cast_choice = findPreference(getString(R.string.set_cast_choice));
assert set_cast_choice != null; assert set_cast_choice != null;
set_cast_choice.setChecked(cast == 1); set_cast_choice.setChecked(cast == 1);

View File

@ -135,6 +135,7 @@ public class Helper {
public static final String VIDEO_ID = "video_id_update"; public static final String VIDEO_ID = "video_id_update";
public static final String APP_PREFS = "app_prefs"; public static final String APP_PREFS = "app_prefs";
public static final String CAST_ID = "D402501A"; public static final String CAST_ID = "D402501A";
public static final String CAST_ID_BITTUBE = "CBA4A31D";
public static final int VIDEOS_PER_PAGE = 10; public static final int VIDEOS_PER_PAGE = 10;
public static final String RECEIVE_ACTION = "receive_action"; public static final String RECEIVE_ACTION = "receive_action";
public static final String SET_UNFOLLOW_VALIDATION = "set_unfollow_validation"; public static final String SET_UNFOLLOW_VALIDATION = "set_unfollow_validation";

View File

@ -32,6 +32,64 @@ public class HelperAcadInstance {
public static String SUBSCRIPTIONS = "ABONNEMENTS"; public static String SUBSCRIPTIONS = "ABONNEMENTS";
public static String MYVIDEOS = "VIDEOS"; public static String MYVIDEOS = "VIDEOS";
public static String[] notOpenId = {
"ac-aix-marseille.fr",
"ac-amiens.fr",
"ac-besancon.fr",
"ac-bordeaux.fr",
"clermont-ferrand.fr",
"ac-corse.fr",
"ac-creteil.fr",
"ac-dijon.fr",
"ac-grenoble.fr",
"ac-lille.fr",
"ac-limoges.fr",
"ac-lyon.fr",
"ac-mayotte.fr",
"ac-montpellier.fr",
"ac-nancy.fr",
"ac-nantes.fr",
"ac-orleans-tours.fr",
"ac-paris.fr",
"ac-poitiers.fr",
"outremer.fr",
"ac-rennes.fr",
"ac-strasbourg.fr",
"ac-toulouse.fr",
"ac-versailles.fr"
};
public static String[] academies = {
"ac-aix-marseille.fr",
"ac-amiens.fr",
"ac-besancon.fr",
"ac-bordeaux.fr",
"clermont-ferrand.fr",
"ac-corse.fr",
"ac-creteil.fr",
"ac-dijon.fr",
"ac-grenoble.fr",
"education.fr",
"ac-lille.fr",
"ac-limoges.fr",
"ac-lyon.fr",
"ac-mayotte.fr",
"ac-montpellier.fr",
"ac-nancy.fr",
"ac-nantes.fr",
"ac-normandie.fr",
"ac-orleans-tours.fr",
"ac-paris.fr",
"ac-poitiers.fr",
"outremer.fr",
"ac-rennes.fr",
"ac-strasbourg.fr",
"ac-toulouse.fr",
"ac-versailles.fr"
};
//List of available emails //List of available emails
public static String[] valideEmails = { public static String[] valideEmails = {
"ac-aix-marseille.fr", "ac-aix-marseille.fr",
@ -71,10 +129,11 @@ public class HelperAcadInstance {
"igesr.gouv.fr" "igesr.gouv.fr"
}; };
static { static {
Map<String, String> is = new LinkedHashMap<>(); Map<String, String> is = new LinkedHashMap<>();
is.put("Normandie", "tube-normandie.beta.education.fr"); //TODO: remove comments when instances will be available
is.put("Enseignement professionnel", "tube-enseignement-professionnel.apps.education.fr"); /* is.put("Enseignement professionnel", "tube-enseignement-professionnel.apps.education.fr");
is.put("Action éducative", "tube-action-educative.apps.education.fr"); is.put("Action éducative", "tube-action-educative.apps.education.fr");
is.put("Numérique éducatif", "tube-numerique-educatif.apps.education.fr"); is.put("Numérique éducatif", "tube-numerique-educatif.apps.education.fr");
is.put("Institutionnel", "tube-institutionnelle.apps.education.fr"); is.put("Institutionnel", "tube-institutionnelle.apps.education.fr");
@ -83,7 +142,7 @@ public class HelperAcadInstance {
is.put("2d - arts, lettres et sciences humaines", "tube-2d-arts-lettres-sciences-humaines.apps.education.fr"); is.put("2d - arts, lettres et sciences humaines", "tube-2d-arts-lettres-sciences-humaines.apps.education.fr");
is.put("Maternelle", "tube-maternelle.apps.education.fr"); is.put("Maternelle", "tube-maternelle.apps.education.fr");
is.put("Cycle 2", "tube-cycle-2.apps.education.fr"); is.put("Cycle 2", "tube-cycle-2.apps.education.fr");
is.put("Cycle 3", "tube-cycle-3.apps.education.fr"); is.put("Cycle 3", "tube-cycle-3.apps.education.fr");*/
instances_themes = Collections.unmodifiableMap(is); instances_themes = Collections.unmodifiableMap(is);
} }

View File

@ -30,10 +30,8 @@ public class HelperInstance {
* @param context Context * @param context Context
* @return String domain instance * @return String domain instance
*/ */
@SuppressWarnings("ConstantConditions")
public static String getLiveInstance(Context context) { public static String getLiveInstance(Context context) {
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String acad;
if (BuildConfig.FLAVOR.compareTo("fdroid_full") == 0 || BuildConfig.FLAVOR.compareTo("google_full") == 0) { if (BuildConfig.FLAVOR.compareTo("fdroid_full") == 0 || BuildConfig.FLAVOR.compareTo("google_full") == 0) {
return sharedpreferences.getString(Helper.PREF_INSTANCE, getDefaultInstance()); return sharedpreferences.getString(Helper.PREF_INSTANCE, getDefaultInstance());
} else if (BuildConfig.FLAVOR.compareTo("bittube") == 0) { } else if (BuildConfig.FLAVOR.compareTo("bittube") == 0) {
@ -41,14 +39,16 @@ public class HelperInstance {
} else if (BuildConfig.FLAVOR.compareTo("queermotion") == 0) { } else if (BuildConfig.FLAVOR.compareTo("queermotion") == 0) {
return sharedpreferences.getString(Helper.PREF_INSTANCE, "queermotion.org"); return sharedpreferences.getString(Helper.PREF_INSTANCE, "queermotion.org");
} else { } else {
acad = sharedpreferences.getString(Helper.PREF_INSTANCE, "tube-institutionnelle.apps.education.fr"); String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, "tube.ac-lyon.fr");
if (acad == null) { if (!instance.startsWith("tube")) {
acad = "tube-institutionnelle.apps.education.fr"; return "tube.ac-lyon.fr";
} else {
return instance;
} }
return acad;
} }
} }
/** /**
* Get a default instance host name depending of the device locale * Get a default instance host name depending of the device locale
* *

View File

@ -244,7 +244,7 @@ public class AccountDAO {
public List<Account> getAllPeertubeAccount() { public List<Account> getAllPeertubeAccount() {
try { try {
Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, Sqlite.COL_SOFTWARE + "=? OR " + Sqlite.COL_SOFTWARE + "is null", new String[]{"PEERTUBE"}, null, null, Sqlite.COL_INSTANCE + " ASC", null); Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, Sqlite.COL_SOFTWARE + "=? OR " + Sqlite.COL_SOFTWARE + " is null", new String[]{"PEERTUBE"}, null, null, Sqlite.COL_INSTANCE + " ASC", null);
return cursorToListUser(c); return cursorToListUser(c);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -115,7 +115,7 @@
android:id="@+id/developer_mastodon" android:id="@+id/developer_mastodon"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="\@TubeLab@toot.fedilab.org" android:text="\@apps@toot.fedilab.org"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
@ -135,23 +135,6 @@
android:textSize="16sp" android:textSize="16sp"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
<TextView
android:id="@+id/github"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Github"
android:textSize="16sp"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/codeberg"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Codeberg"
android:textSize="16sp"
tools:ignore="HardcodedText" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@ -266,7 +249,7 @@
android:layout_marginStart="5dp" android:layout_marginStart="5dp"
android:layout_weight="3" android:layout_weight="3"
android:autoLink="web" android:autoLink="web"
android:text="https://github.com/stom79/TubeLab/issues" android:text="https://framagit.org/imattau/fedilab-tube/-/issues"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
</LinearLayout> </LinearLayout>

View File

@ -81,6 +81,34 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/instance_picker_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/instances_picker"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Spinner
android:id="@+id/instance_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="20dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/instance_picker_title" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrierTop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="instance_picker, login_instance_container" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/login_uid_container" android:id="@+id/login_uid_container"
android:layout_width="0dp" android:layout_width="0dp"
@ -88,7 +116,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/login_instance_container"> app:layout_constraintTop_toBottomOf="@id/barrierTop">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/login_uid" android:id="@+id/login_uid"
@ -121,27 +149,7 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/instance_picker_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/instances_picker"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Spinner
android:id="@+id/instance_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="20dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/login_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/instance_picker_title" />
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier" android:id="@+id/barrier"

View File

@ -44,16 +44,6 @@
app:layout_scrollFlags="scroll|enterAlways" app:layout_scrollFlags="scroll|enterAlways"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
> >
<ImageView
android:id="@+id/instances"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_baseline_public_24"
android:layout_gravity="start"
android:contentDescription="@string/instance_choice"
android:layout_marginEnd="5dp"
app:tint="?attr/colorAccent" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -75,58 +75,9 @@
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:visibility="gone" /> android:visibility="gone" />
<androidx.constraintlayout.widget.ConstraintLayout <include
android:id="@+id/cast_controller" android:id="@+id/min_controller"
android:layout_width="match_parent" layout="@layout/min_controller" />
android:layout_height="match_parent"
android:background="@android:color/black"
android:visibility="gone">
<ImageView
android:id="@+id/cast_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/play"
android:src="@drawable/ic_baseline_play_arrow_32"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cast_loader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/cast_loader_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/please_wait"
android:textColor="?colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/cast_loader_small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.github.ybq.android.spinkit.SpinKitView
android:id="@+id/cast_loader_small"
style="@style/progressBottom"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_gravity="center"
android:layout_marginStart="5dp"
app:SpinKit_Color="?colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/cast_loader_text"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<app.fedilab.fedilabtube.webview.CustomWebview <app.fedilab.fedilabtube.webview.CustomWebview
android:id="@+id/webview_video" android:id="@+id/webview_video"
@ -689,6 +640,5 @@
android:layout_gravity="center" android:layout_gravity="center"
android:layout_margin="30dp" /> android:layout_margin="30dp" />
</RelativeLayout> </RelativeLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -9,17 +9,16 @@
android:title="@string/search" android:title="@string/search"
app:actionViewClass="androidx.appcompat.widget.SearchView" app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always|collapseActionView" /> app:showAsAction="always|collapseActionView" />
<item
android:id="@+id/action_change_instance"
android:icon="@drawable/ic_baseline_track_changes_24"
android:title="@string/change_instance"
android:visible="false"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/action_account" android:id="@+id/action_account"
app:actionLayout="@layout/counter_account_icon" app:actionLayout="@layout/counter_account_icon"
android:title="@string/account" android:title="@string/account"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item
android:id="@+id/action_change_instance"
android:icon="@drawable/ic_baseline_track_changes_24"
android:title="@string/change_instance"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/action_playlist" android:id="@+id/action_playlist"
android:icon="@drawable/ic_baseline_playlist_play_24" android:icon="@drawable/ic_baseline_playlist_play_24"

View File

@ -377,4 +377,6 @@
<string name="subscription_cancelled">Subscription cancelled!</string> <string name="subscription_cancelled">Subscription cancelled!</string>
<string name="cancel_subscription">Cancel subscription</string> <string name="cancel_subscription">Cancel subscription</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string> <string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>

View File

@ -373,4 +373,6 @@
<string name="subscription_cancelled">Subscription cancelled!</string> <string name="subscription_cancelled">Subscription cancelled!</string>
<string name="cancel_subscription">Cancel subscription</string> <string name="cancel_subscription">Cancel subscription</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string> <string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>

View File

@ -372,4 +372,6 @@
<string name="subscription_cancelled">Subscription cancelled!</string> <string name="subscription_cancelled">Subscription cancelled!</string>
<string name="cancel_subscription">Cancel subscription</string> <string name="cancel_subscription">Cancel subscription</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string> <string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>

View File

@ -373,4 +373,6 @@
<string name="subscription_cancelled">Subscription cancelled!</string> <string name="subscription_cancelled">Subscription cancelled!</string>
<string name="cancel_subscription">Cancel subscription</string> <string name="cancel_subscription">Cancel subscription</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string> <string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>

View File

@ -372,4 +372,6 @@
<string name="subscription_cancelled">Subscription cancelled!</string> <string name="subscription_cancelled">Subscription cancelled!</string>
<string name="cancel_subscription">Cancel subscription</string> <string name="cancel_subscription">Cancel subscription</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string> <string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>

View File

@ -372,4 +372,6 @@
<string name="subscription_cancelled">Subscription cancelled!</string> <string name="subscription_cancelled">Subscription cancelled!</string>
<string name="cancel_subscription">Cancel subscription</string> <string name="cancel_subscription">Cancel subscription</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string> <string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>

View File

@ -373,4 +373,6 @@
<string name="subscription_cancelled">Subscription cancelled!</string> <string name="subscription_cancelled">Subscription cancelled!</string>
<string name="cancel_subscription">Cancel subscription</string> <string name="cancel_subscription">Cancel subscription</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string> <string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>

View File

@ -375,4 +375,6 @@
<string name="subscription_cancelled">Subscription cancelled!</string> <string name="subscription_cancelled">Subscription cancelled!</string>
<string name="cancel_subscription">Cancel subscription</string> <string name="cancel_subscription">Cancel subscription</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string> <string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>

View File

@ -373,4 +373,6 @@
<string name="subscription_cancelled">Subscription cancelled!</string> <string name="subscription_cancelled">Subscription cancelled!</string>
<string name="cancel_subscription">Cancel subscription</string> <string name="cancel_subscription">Cancel subscription</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string> <string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>

View File

@ -133,9 +133,9 @@
<string name="action_follow">Подписка</string> <string name="action_follow">Подписка</string>
<string name="action_mute">Игнорировать</string> <string name="action_mute">Игнорировать</string>
<string name="unlimited">Без ограничений</string> <string name="unlimited">Без ограничений</string>
<string name="peers">%1$d Peers</string> <string name="peers">%1$d пиров</string>
<string name="b">B</string> <string name="b">Б</string>
<string name="kb">KB</string> <string name="kb">КБ</string>
<string name="mb">МБ</string> <string name="mb">МБ</string>
<string name="gb">ГБ</string> <string name="gb">ГБ</string>
<string name="total_video_quota">Общая квота видео</string> <string name="total_video_quota">Общая квота видео</string>
@ -357,22 +357,24 @@
<string name="instance_not_availabe">Экземпляр недоступен!</string> <string name="instance_not_availabe">Экземпляр недоступен!</string>
<string name="max_tag_size">На видео не должно быть более 5 тегов!</string> <string name="max_tag_size">На видео не должно быть более 5 тегов!</string>
<string name="watermark">Водяной знак</string> <string name="watermark">Водяной знак</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</string> <string name="toast_code_error">Произошла ошибка! Экземпляр не вернул код авторизации!</string>
<string name="remote_account_from"><b>%1$s</b> remote account connected with the app.\n\nYou can proceed to some limited actions.</string> <string name="remote_account_from"><b>%1$s</b> удаленная учетная запись, связанная с приложением.\n\nВы можете перейти к некоторым ограниченным действиям.</string>
<string name="donate">Donate</string> <string name="donate">Пожертвование</string>
<string name="my_donations">My donations</string> <string name="my_donations">Мои пожертвования</string>
<string name="one_time_donation_text">Here, you can make a one time donation for supporting the development of the app. This action will not bring extra features!</string> <string name="one_time_donation_text">Здесь вы можете сделать единовременное пожертвование для поддержки разработки приложения. Это действие не принесет дополнительных возможностей!</string>
<string name="recurrent_donation_text">Here, you can make a recurrent donation for supporting the development of the app. This action will not bring extra features!</string> <string name="recurrent_donation_text">Здесь вы можете сделать единовременное пожертвование для поддержки разработки приложения. Это действие не принесет дополнительных возможностей!</string>
<string name="make_a_donation">Make a donation</string> <string name="make_a_donation">Сделать пожертвование</string>
<string name="donations_description">Here you will find the list of your recurrent donations made to support the development of the app! Thank you!</string> <string name="donations_description">Здесь вы найдете список ваших периодических пожертвований, сделанных для поддержки разработки приложения! Спасибо!</string>
<string name="support_the_app">Support the app</string> <string name="support_the_app">Поддержка приложения</string>
<string name="donation_cancelled">Donation has been cancelled!</string> <string name="donation_cancelled">Пожертвование было отменено!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string> <string name="donation_succeeded_null">Благодарим Вас за ваше пожертвование!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string> <string name="donation_succeeded">Спасибо за пожертвование в %1$s!</string>
<string name="one_time">One time</string> <string name="one_time">Один раз</string>
<string name="my_subscriptions">My subscription</string> <string name="my_subscriptions">Моя подписка</string>
<string name="month">Month</string> <string name="month">Месяц</string>
<string name="subscription_cancelled">Subscription cancelled!</string> <string name="subscription_cancelled">Подписка отменена!</string>
<string name="cancel_subscription">Cancel subscription</string> <string name="cancel_subscription">Отменить подписку</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string> <string name="cancel_subscription_confirm">Вы уверены, что хотите отменить эту подписку?</string>
<string name="mark_all_notifications_as_read_confirm">Вы уверены, что хотите отметить все уведомления как прочитанные?</string>
<string name="mark_all_as_read">Отметить все как прочитанные</string>
</resources> </resources>

View File

@ -373,4 +373,6 @@
<string name="subscription_cancelled">Subscription cancelled!</string> <string name="subscription_cancelled">Subscription cancelled!</string>
<string name="cancel_subscription">Cancel subscription</string> <string name="cancel_subscription">Cancel subscription</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string> <string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>

View File

@ -371,4 +371,6 @@
<string name="subscription_cancelled">Subscription cancelled!</string> <string name="subscription_cancelled">Subscription cancelled!</string>
<string name="cancel_subscription">Cancel subscription</string> <string name="cancel_subscription">Cancel subscription</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string> <string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>

View File

@ -372,4 +372,6 @@
<string name="subscription_cancelled">Subscription cancelled!</string> <string name="subscription_cancelled">Subscription cancelled!</string>
<string name="cancel_subscription">Cancel subscription</string> <string name="cancel_subscription">Cancel subscription</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string> <string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
<string name="mark_all_notifications_as_read_confirm">Are you sure you want to mark all notifications as read?</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>

View File

@ -0,0 +1,261 @@
package app.fedilab.fedilabtube;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of TubeLab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import app.fedilab.fedilabtube.client.data.VideoData;
import app.fedilab.fedilabtube.databinding.ActivityMainBinding;
import app.fedilab.fedilabtube.helper.Helper;
import su.litvak.chromecast.api.v2.ChromeCast;
import su.litvak.chromecast.api.v2.ChromeCasts;
import su.litvak.chromecast.api.v2.ChromeCastsListener;
import su.litvak.chromecast.api.v2.MediaStatus;
public abstract class BaseMainActivity extends AppCompatActivity implements ChromeCastsListener {
public static List<ChromeCast> chromeCasts;
public static ChromeCast chromeCast;
public static boolean chromecastActivated = false;
protected ActivityMainBinding binding;
private BroadcastReceiver manage_chromecast;
private VideoData.Video castedTube;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
ChromeCastsListener chromeCastsListener = this;
ChromeCasts.registerListener(chromeCastsListener);
binding.castClose.setOnClickListener(v -> {
Intent intentBC = new Intent(Helper.RECEIVE_CAST_SETTINGS);
Bundle b = new Bundle();
b.putInt("displayed", 0);
intentBC.putExtras(b);
LocalBroadcastManager.getInstance(BaseMainActivity.this).sendBroadcast(intentBC);
});
binding.castTogglePlay.setOnClickListener(v -> {
if (chromeCast != null) {
new Thread(() -> {
try {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> binding.castTogglePlay.setVisibility(View.GONE);
mainHandler.post(myRunnable);
int icon = -1;
if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PLAYING) {
chromeCast.pause();
icon = R.drawable.ic_baseline_play_arrow_32;
} else if (chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PAUSED) {
chromeCast.play();
icon = R.drawable.ic_baseline_pause_32;
}
if (icon != -1) {
int finalIcon = icon;
myRunnable = () -> binding.castTogglePlay.setImageResource(finalIcon);
mainHandler.post(myRunnable);
}
myRunnable = () -> binding.castTogglePlay.setVisibility(View.VISIBLE);
mainHandler.post(myRunnable);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
});
manage_chromecast = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Bundle b = intent.getExtras();
assert b != null;
int state = b.getInt("state_asked", -1);
int displayed = b.getInt("displayed", -1);
castedTube = b.getParcelable("castedTube");
if (state == 1) {
discoverCast();
} else if (state == 0) {
new Thread(() -> {
try {
if (chromeCast != null) {
chromeCast.stopApp();
chromeCast.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
if (displayed == 1) {
chromecastActivated = true;
if (castedTube != null) {
binding.castInfo.setVisibility(View.VISIBLE);
Helper.loadGiF(BaseMainActivity.this, castedTube.getThumbnailPath(), binding.castView);
binding.castTitle.setText(castedTube.getTitle());
binding.castDescription.setText(castedTube.getDescription());
}
} else if (displayed == 0) {
chromecastActivated = false;
binding.castInfo.setVisibility(View.GONE);
new Thread(() -> {
try {
if (chromeCast != null) {
chromeCast.stopApp();
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
};
LocalBroadcastManager.getInstance(BaseMainActivity.this).registerReceiver(manage_chromecast, new IntentFilter(Helper.RECEIVE_CAST_SETTINGS));
}
@Override
public void newChromeCastDiscovered(ChromeCast chromeCast) {
if (chromeCasts == null) {
chromeCasts = new ArrayList<>();
chromeCasts.add(chromeCast);
} else {
boolean canBeAdded = true;
for (ChromeCast cast : chromeCasts) {
if (cast.getName().compareTo(chromeCast.getName()) == 0) {
canBeAdded = false;
break;
}
}
if (canBeAdded) {
chromeCasts.add(chromeCast);
}
}
try {
if (chromeCast.isAppRunning(Helper.CAST_ID) && chromeCast.getMediaStatus() != null && chromeCast.getMediaStatus().playerState != null) {
if (binding.castInfo.getVisibility() == View.GONE) {
binding.castInfo.setVisibility(View.VISIBLE);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void chromeCastRemoved(ChromeCast chromeCast) {
}
@Override
public void onDestroy() {
super.onDestroy();
ChromeCasts.unregisterListener(this);
if (manage_chromecast != null) {
LocalBroadcastManager.getInstance(BaseMainActivity.this).unregisterReceiver(manage_chromecast);
new Thread(() -> {
if (chromeCasts != null && chromeCasts.size() > 0) {
for (ChromeCast cast : chromeCasts) {
try {
cast.stopApp();
cast.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
if (chromeCasts != null) {
chromeCasts = null;
}
if (chromeCast != null) {
chromeCast = null;
}
}
//Method for discovering cast devices
public void discoverCast() {
new Thread(() -> {
if (chromeCasts != null) {
for (ChromeCast cast : chromeCasts) {
try {
cast.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
chromeCasts = null;
}
chromeCasts = new ArrayList<>();
try {
List<NetworkInterface> interfaces;
interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface ni : interfaces) {
if ((!ni.isLoopback()) && ni.isUp() && (ni.getName().equals("wlan0"))) {
Enumeration<InetAddress> inetAddressEnumeration = ni.getInetAddresses();
while (inetAddressEnumeration.hasMoreElements()) {
InetAddress inetAddress = inetAddressEnumeration.nextElement();
ChromeCasts.restartDiscovery(inetAddress);
int tryFind = 0;
while (ChromeCasts.get().isEmpty() && tryFind < 5) {
try {
//noinspection BusyWait
Thread.sleep(1000);
tryFind++;
} catch (InterruptedException ignored) {
}
}
}
}
}
ChromeCasts.stopDiscovery();
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = this::invalidateOptionsMenu;
mainHandler.post(myRunnable);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}

View File

@ -0,0 +1,205 @@
package app.fedilab.fedilabtube;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.webkit.MimeTypeMap;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.android.exoplayer2.SimpleExoPlayer;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.security.GeneralSecurityException;
import app.fedilab.fedilabtube.client.data.VideoData;
import app.fedilab.fedilabtube.databinding.ActivityPeertubeBinding;
import app.fedilab.fedilabtube.helper.Helper;
import su.litvak.chromecast.api.v2.ChromeCast;
import su.litvak.chromecast.api.v2.MediaStatus;
import su.litvak.chromecast.api.v2.Status;
import static app.fedilab.fedilabtube.helper.Helper.CAST_ID;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of TubeLab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
public class BasePeertubeActivity extends AppCompatActivity {
protected ActivityPeertubeBinding binding;
protected VideoData.Video peertube;
protected SimpleExoPlayer player;
protected String videoURL;
protected String subtitlesStr;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityPeertubeBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
binding.minController.castPlay.setOnClickListener(v -> {
binding.minController.castLoader.setVisibility(View.VISIBLE);
if (BaseMainActivity.chromeCast != null) {
new Thread(() -> {
try {
int icon = -1;
if (BaseMainActivity.chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PLAYING) {
BaseMainActivity.chromeCast.pause();
icon = R.drawable.ic_baseline_play_arrow_32;
} else if (BaseMainActivity.chromeCast.getMediaStatus().playerState == MediaStatus.PlayerState.PAUSED) {
BaseMainActivity.chromeCast.play();
icon = R.drawable.ic_baseline_pause_32;
}
if (icon != -1) {
Handler mainHandler = new Handler(Looper.getMainLooper());
int finalIcon = icon;
Runnable myRunnable = () -> binding.minController.castPlay.setImageResource(finalIcon);
mainHandler.post(myRunnable);
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> binding.minController.castLoader.setVisibility(View.GONE);
mainHandler.post(myRunnable);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_cast) {
if (BaseMainActivity.chromeCasts != null && BaseMainActivity.chromeCasts.size() > 0) {
String[] chromecast_choice = new String[BaseMainActivity.chromeCasts.size()];
AlertDialog.Builder alt_bld = new AlertDialog.Builder(this);
alt_bld.setTitle(R.string.chromecast_choice);
int i = 0;
for (ChromeCast cc : BaseMainActivity.chromeCasts) {
chromecast_choice[i] = cc.getTitle();
i++;
}
i = 0;
for (ChromeCast cc : BaseMainActivity.chromeCasts) {
if (BaseMainActivity.chromecastActivated && cc.isConnected()) {
break;
}
i++;
}
alt_bld.setSingleChoiceItems(chromecast_choice, i, (dialog, position) -> {
BaseMainActivity.chromeCast = BaseMainActivity.chromeCasts.get(position);
new Thread(() -> {
if (BaseMainActivity.chromeCast != null) {
Intent intentBC = new Intent(Helper.RECEIVE_CAST_SETTINGS);
Bundle b = new Bundle();
if (BaseMainActivity.chromecastActivated) {
b.putInt("displayed", 0);
intentBC.putExtras(b);
LocalBroadcastManager.getInstance(BasePeertubeActivity.this).sendBroadcast(intentBC);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
binding.doubleTapPlayerView.setVisibility(View.VISIBLE);
binding.minController.castMiniController.setVisibility(View.GONE);
};
mainHandler.post(myRunnable);
} else {
b.putInt("displayed", 1);
b.putParcelable("castedTube", peertube);
intentBC.putExtras(b);
LocalBroadcastManager.getInstance(BasePeertubeActivity.this).sendBroadcast(intentBC);
try {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
invalidateOptionsMenu();
binding.minController.castLoader.setVisibility(View.VISIBLE);
player.setPlayWhenReady(false);
binding.doubleTapPlayerView.setVisibility(View.GONE);
binding.minController.castMiniController.setVisibility(View.VISIBLE);
dialog.dismiss();
if (videoURL != null) {
if (player != null && player.getCurrentPosition() > 0) {
videoURL += "?start=" + (player.getCurrentPosition() / 1000);
}
}
};
mainHandler.post(myRunnable);
if (!BaseMainActivity.chromeCast.isConnected()) {
BaseMainActivity.chromeCast.connect();
}
myRunnable = this::invalidateOptionsMenu;
mainHandler.post(myRunnable);
Status status = BaseMainActivity.chromeCast.getStatus();
if (BaseMainActivity.chromeCast.isAppAvailable(CAST_ID) && !status.isAppRunning(CAST_ID)) {
BaseMainActivity.chromeCast.launchApp(CAST_ID);
}
if (videoURL != null) {
String mime = MimeTypeMap.getFileExtensionFromUrl(videoURL);
BaseMainActivity.chromeCast.setRequestTimeout(60000);
BaseMainActivity.chromeCast.load(peertube.getTitle(), null, videoURL, mime);
BaseMainActivity.chromeCast.play();
binding.minController.castPlay.setImageResource(R.drawable.ic_baseline_pause_32);
}
myRunnable = () -> binding.minController.castLoader.setVisibility(View.GONE);
mainHandler.post(myRunnable);
} catch (IOException | GeneralSecurityException e) {
e.printStackTrace();
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
invalidateOptionsMenu();
dialog.dismiss();
};
mainHandler.post(myRunnable);
}
}).start();
});
alt_bld.setPositiveButton(R.string.close, (dialog, id) -> dialog.dismiss());
AlertDialog alert = alt_bld.create();
alert.show();
}
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onCreateOptionsMenu(@NotNull Menu menu) {
getMenuInflater().inflate(R.menu.video_menu, menu);
MenuItem castItem = menu.findItem(R.id.action_cast);
if (BaseMainActivity.chromeCasts != null && BaseMainActivity.chromeCasts.size() > 0) {
castItem.setVisible(true);
if (BaseMainActivity.chromeCast != null && BaseMainActivity.chromeCast.isConnected()) {
castItem.setIcon(R.drawable.ic_baseline_cast_connected_24);
} else {
castItem.setIcon(R.drawable.ic_baseline_cast_24);
}
}
return true;
}
}

View File

@ -0,0 +1,18 @@
package app.fedilab.fedilabtube.provider;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of TubeLab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
public class CastOptionsProvider {
}

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/castMiniController"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:visibility="gone">
<ImageView
android:id="@+id/cast_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/play"
android:src="@drawable/ic_baseline_play_arrow_32"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cast_loader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/cast_loader_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/please_wait"
android:textColor="?colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/cast_loader_small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.github.ybq.android.spinkit.SpinKitView
android:id="@+id/cast_loader_small"
style="@style/progressBottom"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_gravity="center"
android:layout_marginStart="5dp"
app:SpinKit_Color="?colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/cast_loader_text"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -6,7 +6,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.1' classpath 'com.android.tools.build:gradle:4.1.2'
def nav_version = "2.3.0" def nav_version = "2.3.0"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'

23
frostwire-jlibtorrent/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
*~
.DS_Store
*.ipr
*.iws
.idea
.gradle
/build
/out
/android/build
/android/src/main/jniLibs
/java/out/
*.o
/jlibtorrent.xcodeproj/xcuserdata/
/obj
frostwire-jlibtorrent.i*
/test-libtorrent
/swig/bin
/swig/build
/libjlibtorrent*.dylib
/libjlibtorrent*.so
/jlibtorrent*.dll
/node/jlibtorrent.node
/dht_shell.dat

View File

@ -0,0 +1,259 @@
language: cpp
dist: eoan
matrix:
include:
- env: os_build=android os_arch=arm android_api=19
- env: os_build=android os_arch=arm64 android_api=21
- env: os_build=android os_arch=x86 android_api=19
- env: os_build=android os_arch=x86_64 android_api=21
- env: os_build=linux os_arch=x86
- env: os_build=linux os_arch=x86_64
- env: os_build=windows os_arch=x86
- env: os_build=windows os_arch=x86_64
- os: osx
env: os_build=macosx os_arch=x86_64
osx_image: xcode11.2
branches:
only:
- master
before_install:
- wget -nv -O boost.zip https://dl.bintray.com/boostorg/release/1.73.0/source/boost_1_73_0.zip
- unzip -qq boost.zip
- cd boost_1_73_0
- ls -lth
- ./bootstrap.sh
- ./b2
- cd ..
- export BOOST_ROOT=$PWD/boost_1_73_0
- export BOOST_BUILD_PATH=$BOOST_ROOT
# openssl: download
- wget -nv -O openssl.tar.gz https://www.openssl.org/source/openssl-1.1.1i.tar.gz
- tar xzf openssl.tar.gz
- export OPENSSL_SOURCE=$PWD/openssl-1.1.1i
# fix source code
# avoid GDI dependency in windows, fix double return statement on threads_none.c
- if [ $os_build == "windows" ]; then
sed -i 's/if defined(_WIN32_WINNT) && _WIN32_WINNT>=0x0333/if 0/g' $OPENSSL_SOURCE/crypto/cryptlib.c;
sed -i 's/MessageBox.*//g' $OPENSSL_SOURCE/crypto/cryptlib.c;
sed -i 's/return return 0;/return 0;/g' $OPENSSL_SOURCE/crypto/threads_none.c;
fi
# libtorrent: download and checkout revision
- git clone https://github.com/arvidn/libtorrent
- cd libtorrent
- git checkout 77172672c8db2f6bcef17d867d9a81faae77ec1e
- git submodule init
- git submodule update
- cd ..
- export LIBTORRENT_ROOT=$PWD/libtorrent
# android ndk: download and create toolchain
- if [ $os_build == "android" ]; then
wget -nv -O android-ndk.zip https://dl.google.com/android/repository/android-ndk-r21d-linux-x86_64.zip;
echo "Extracting NDK...wait";
unzip -qq android-ndk.zip;
export NDK=$PWD/android-ndk-r21d;
$NDK/build/tools/make_standalone_toolchain.py --arch $os_arch --api $android_api --stl libc++ --install-dir android-toolchain;
export ANDROID_TOOLCHAIN=$PWD/android-toolchain;
fi
- if [[ $os_build == "linux" || $os_build == "windows" ]]; then
sudo apt-get install -qq g++-7 g++-7-multilib;
fi
- if [[ $os_build == "linux" && $os_arch == "x86" ]]; then
sudo apt-get install -qq libc6-dev-i386;
sudo apt-get install -qq lib32gcc-5-dev;
sudo apt-get install -qq lib32stdc++-5-dev;
sudo ln -s /usr/include/asm-generic /usr/include/asm;
fi
# linux cross compilation tools for windows development
# remove files related to libwinpthread dll
- if [[ $os_build == "windows" && $os_arch == "x86" ]]; then
sudo apt-get install -qq g++-mingw-w64-i686;
fi
- if [[ $os_build == "windows" && $os_arch == "x86_64" ]]; then
sudo apt-get install -qq g++-mingw-w64-x86-64;
fi
# install gradle in macOS to run the unit tests
- if [ $os_build == "macosx" ]; then
brew update > /dev/null && brew install gradle;
fi
# openssl
- 'export OPENSSL_NO_OPTS="no-afalgeng no-async no-autoalginit no-autoerrinit
no-capieng no-cms no-comp no-deprecated no-dgram no-dso no-dtls
no-dynamic-engine no-egd no-engine no-err no-filenames no-gost no-hw
no-makedepend no-multiblock no-nextprotoneg no-posix-io no-psk
no-rdrand no-sctp no-shared no-sock no-srp no-srtp no-static-engine
no-stdio no-threads no-ui-console no-zlib no-zlib-dynamic
-fno-strict-aliasing -fvisibility=hidden -Os"'
# android-arm
- if [[ $os_build == "android" && $os_arch == "arm" ]]; then
export CC=$ANDROID_TOOLCHAIN/bin/arm-linux-androideabi-clang;
export run_openssl_configure="./Configure linux-armv4 ${OPENSSL_NO_OPTS} -march=armv7-a -mfpu=neon -fPIC --prefix=$OPENSSL_SOURCE/../openssl";
fi
# android-arm64
- if [[ $os_build == "android" && $os_arch == "arm64" ]]; then
export CC=$ANDROID_TOOLCHAIN/bin/aarch64-linux-android-clang;
export run_openssl_configure="./Configure linux-aarch64 ${OPENSSL_NO_OPTS} -march=armv8-a+crypto -fPIC --prefix=$OPENSSL_SOURCE/../openssl";
fi
# android-x86
- if [[ $os_build == "android" && $os_arch == "x86" ]]; then
export CC=$ANDROID_TOOLCHAIN/bin/i686-linux-android-clang;
export run_openssl_configure="./Configure linux-elf ${OPENSSL_NO_OPTS} -fPIC -mstackrealign --prefix=$OPENSSL_SOURCE/../openssl";
fi
# android-x86_64
- if [[ $os_build == "android" && $os_arch == "x86_64" ]]; then
export CC=$ANDROID_TOOLCHAIN/bin/x86_64-linux-android-clang;
export run_openssl_configure="./Configure linux-x86_64 ${OPENSSL_NO_OPTS} -fPIC --prefix=$OPENSSL_SOURCE/../openssl";
fi
# linux-x86
- if [[ $os_build == "linux" && $os_arch == "x86" ]]; then
export CC=gcc-7;
export run_openssl_configure="./Configure linux-elf ${OPENSSL_NO_OPTS} -fPIC -m32 --prefix=$OPENSSL_SOURCE/../openssl";
fi
# linux-x86_64
- if [[ $os_build == "linux" && $os_arch == "x86_64" ]]; then
export CC=gcc-7;
export run_openssl_configure="./Configure linux-x86_64 ${OPENSSL_NO_OPTS} -fPIC --prefix=$OPENSSL_SOURCE/../openssl";
fi
# windows-x86
- if [[ $os_build == "windows" && $os_arch == "x86" ]]; then
export CC=i686-w64-mingw32-gcc-posix;
export run_openssl_configure="./Configure mingw ${OPENSSL_NO_OPTS} --prefix=$OPENSSL_SOURCE/../openssl";
fi
# windows-x86_64
- if [[ $os_build == "windows" && $os_arch == "x86_64" ]]; then
export CC=x86_64-w64-mingw32-gcc-posix;
export run_openssl_configure="./Configure mingw64 ${OPENSSL_NO_OPTS} --prefix=$OPENSSL_SOURCE/../openssl";
fi
# macosx
- if [ $os_build == "macosx" ]; then
export run_openssl_configure="./Configure darwin64-x86_64-cc ${OPENSSL_NO_OPTS} --prefix=$OPENSSL_SOURCE/../openssl";
fi
# jlibtorrent
# android-arm
- if [[ $os_build == "android" && $os_arch == "arm" ]]; then
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/android-arm-config.jam variant=release toolset=clang-linux-arm target-os=android location=bin/release/android/armeabi-v7a";
export run_objcopy="${ANDROID_TOOLCHAIN}/bin/arm-linux-androideabi-objcopy --only-keep-debug bin/release/android/armeabi-v7a/libjlibtorrent.so bin/release/android/armeabi-v7a/libjlibtorrent.so.debug";
export run_strip="${ANDROID_TOOLCHAIN}/bin/arm-linux-androideabi-strip --strip-unneeded -x -g bin/release/android/armeabi-v7a/libjlibtorrent.so";
export run_readelf="${ANDROID_TOOLCHAIN}/bin/arm-linux-androideabi-readelf -d bin/release/android/armeabi-v7a/libjlibtorrent.so";
export PATH=$ANDROID_TOOLCHAIN/arm-linux-androideabi/bin:$PATH;
sed -i 's/RANLIB = ranlib/RANLIB = "${ANDROID_TOOLCHAIN}\/bin\/arm-linux-androideabi-ranlib"/g' ${BOOST_ROOT}/tools/build/src/tools/gcc.jam;
fi
# android-arm64
- if [[ $os_build == "android" && $os_arch == "arm64" ]]; then
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/android-arm64-config.jam variant=release toolset=clang-arm64 target-os=android location=bin/release/android/arm64-v8a";
export run_objcopy="${ANDROID_TOOLCHAIN}/bin/aarch64-linux-android-objcopy --only-keep-debug bin/release/android/arm64-v8a/libjlibtorrent.so bin/release/android/arm64-v8a/libjlibtorrent.so.debug";
export run_strip="${ANDROID_TOOLCHAIN}/bin/aarch64-linux-android-strip --strip-unneeded -x bin/release/android/arm64-v8a/libjlibtorrent.so";
export run_readelf="${ANDROID_TOOLCHAIN}/bin/aarch64-linux-android-readelf -d bin/release/android/arm64-v8a/libjlibtorrent.so";
export PATH=$ANDROID_TOOLCHAIN/aarch64-linux-android/bin:$PATH;
sed -i 's/RANLIB = ranlib/RANLIB = "${ANDROID_TOOLCHAIN}\/bin\/aarch64-linux-android-ranlib"/g' ${BOOST_ROOT}/tools/build/src/tools/gcc.jam;
fi
# android-x86
- if [[ $os_build == "android" && $os_arch == "x86" ]]; then
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/android-x86-config.jam variant=release toolset=clang-x86 target-os=android location=bin/release/android/x86";
export run_objcopy="${ANDROID_TOOLCHAIN}/bin/i686-linux-android-objcopy --only-keep-debug bin/release/android/x86/libjlibtorrent.so bin/release/android/x86/libjlibtorrent.so.debug";
export run_strip="${ANDROID_TOOLCHAIN}/bin/i686-linux-android-strip --strip-unneeded -x -g bin/release/android/x86/libjlibtorrent.so";
export run_readelf="${ANDROID_TOOLCHAIN}/bin/i686-linux-android-readelf -d bin/release/android/x86/libjlibtorrent.so";
export PATH=$ANDROID_TOOLCHAIN/i686-linux-android/bin:$PATH;
sed -i 's/RANLIB = ranlib/RANLIB = "${ANDROID_TOOLCHAIN}\/bin\/i686-linux-android-ranlib"/g' ${BOOST_ROOT}/tools/build/src/tools/gcc.jam;
fi
# android-x86_64
- if [[ $os_build == "android" && $os_arch == "x86_64" ]]; then
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/android-x86_64-config.jam variant=release toolset=clang-x86_64 target-os=android location=bin/release/android/x86_64";
export run_objcopy="${ANDROID_TOOLCHAIN}/bin/x86_64-linux-android-objcopy --only-keep-debug bin/release/android/x86_64/libjlibtorrent.so bin/release/android/x86_64/libjlibtorrent.so.debug";
export run_strip="${ANDROID_TOOLCHAIN}/bin/x86_64-linux-android-strip --strip-unneeded -x bin/release/android/x86_64/libjlibtorrent.so";
export run_readelf="${ANDROID_TOOLCHAIN}/bin/x86_64-linux-android-readelf -d bin/release/android/x86_64/libjlibtorrent.so";
export PATH=$ANDROID_TOOLCHAIN/x86_64-linux-android/bin:$PATH;
sed -i 's/RANLIB = ranlib/RANLIB = "${ANDROID_TOOLCHAIN}\/bin\/x86_64-linux-android-ranlib"/g' ${BOOST_ROOT}/tools/build/src/tools/gcc.jam;
fi
# linux-x86
- if [[ $os_build == "linux" && $os_arch == "x86" ]]; then
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/linux-x86-config.jam variant=release toolset=gcc-x86 target-os=linux location=bin/release/linux/x86";
export run_objcopy="objcopy --only-keep-debug bin/release/linux/x86/libjlibtorrent.so bin/release/linux/x86/libjlibtorrent.so.debug";
export run_strip="strip --strip-unneeded -x bin/release/linux/x86/libjlibtorrent.so";
export run_readelf="readelf -d bin/release/linux/x86/libjlibtorrent.so";
fi
# linux-x86_64
- if [[ $os_build == "linux" && $os_arch == "x86_64" ]]; then
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/linux-x86_64-config.jam variant=release toolset=gcc-x86_64 target-os=linux location=bin/release/linux/x86_64";
export run_objcopy="objcopy --only-keep-debug bin/release/linux/x86_64/libjlibtorrent.so bin/release/linux/x86_64/libjlibtorrent.so.debug";
export run_strip="strip --strip-unneeded -x bin/release/linux/x86_64/libjlibtorrent.so";
export run_readelf="readelf -d bin/release/linux/x86_64/libjlibtorrent.so";
fi
# windows-x86
- if [[ $os_build == "windows" && $os_arch == "x86" ]]; then
sed -i 's/ JNICALL Java_com_frostwire/ JNICALL _Java_com_frostwire/g' swig/libtorrent_jni.cpp;
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/windows-x86-config.jam variant=release toolset=gcc-x86 target-os=windows location=bin/release/windows/x86";
export run_strip="i686-w64-mingw32-strip --strip-unneeded -x bin/release/windows/x86/libjlibtorrent.dll";
export run_readelf="eval objdump -p bin/release/windows/x86/jlibtorrent.dll | grep DLL";
fi
# windows-x86_64
- if [[ $os_build == "windows" && $os_arch == "x86_64" ]]; then
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/windows-x86_64-config.jam variant=release toolset=gcc-x86_64 target-os=windows location=bin/release/windows/x86_64";
export run_strip="x86_64-w64-mingw32-strip --strip-unneeded -x bin/release/windows/x86_64/libjlibtorrent.dll";
export run_readelf="eval objdump -p bin/release/windows/x86_64/jlibtorrent.dll | grep DLL";
fi
# macosx
- if [ $os_build == "macosx" ]; then
export run_bjam="${BOOST_ROOT}/b2 --user-config=config/macosx-x86_64-config.jam variant=release toolset=darwin-x86_64 target-os=darwin location=bin/release/macosx/x86_64";
export run_strip="strip -S -x bin/release/macosx/x86_64/libjlibtorrent.dylib";
export run_readelf="otool -L bin/release/macosx/x86_64/libjlibtorrent.dylib";
fi
script:
- cd $OPENSSL_SOURCE
- $run_openssl_configure
- echo "Compiling openssl...(remove &> /dev/null to see output)"
- make #&> /dev/null
- make install_sw &> /dev/null
- cd ..
- export OPENSSL_ROOT=$PWD/openssl
- cd swig
- $run_bjam
- $run_objcopy
- $run_strip
- if [[ $os_build == "windows" && $os_arch == "x86" ]]; then
mv bin/release/windows/x86/libjlibtorrent.dll bin/release/windows/x86/jlibtorrent.dll;
fi
- if [[ $os_build == "windows" && $os_arch == "x86_64" ]]; then
mv bin/release/windows/x86_64/libjlibtorrent.dll bin/release/windows/x86_64/jlibtorrent.dll;
fi
- if [ $os_build == "macosx" ]; then
cd ..;
cp swig/bin/release/macosx/x86_64/libjlibtorrent.dylib .;
gradle test;
cd swig;
fi
- $run_readelf
- cd ..
before_deploy:
- if [ -d swig/bin ]; then
cd swig/bin;
find . -type f | egrep -v '.*\.so$|.*\.dll$|.*\.dylib$|.*\.debug$' | xargs rm;
find . -empty -type d | xargs rm -r;
fi
- cd ../..
deploy:
provider: s3
access_key_id: $S3_ACCESS_KEY
secret_access_key: $S3_SECRET_KEY
bucket: $S3_BUCKET
skip_cleanup: true
local_dir: swig/bin
on:
all_branches: true

View File

@ -0,0 +1,4 @@
bump version numberc in:
- build.gradle 'version', then run swig/run-swig.sh, then build-xxx.sh and gradle build
- changelog.txt

View File

@ -0,0 +1 @@
github: gubatron

View File

@ -0,0 +1,23 @@
The MIT License
===============
Copyright (C) 2016 FrostWire, LLC.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject
to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,131 @@
frostwire-jlibtorrent
=====================
![JLibtorrent Logo](logo/jlibtorrent_logo_color.png)
A swig Java interface for libtorrent by the makers of FrostWire.
Develop libtorrent based apps with the joy of coding in Java.
[Discord Developer Chatroom](https://discord.com/channels/461752211802947585/461766946669461515)
Using
========
[Download the latest release .jars](https://github.com/frostwire/frostwire-jlibtorrent/releases)
All platforms will need you to use at least 2 `.jar` files.
The `.jar` with the java classes -> `jlibtorrent-w.x.y.z.jar` and a secondary `.jar`s containing the JNI binary library for the particular OS and CPU architecture.
In the case of desktop operating systems, you might want to extract the shared library inside the jar (.dll, .so, .dylib) and place it in a folder specified by the `java.library.path`
The secondary jars are:
- Windows: `jlibtorrent-windows-w.x.y.z.jar` (x86 and x86_64 .dlls)
- Mac: `jlibtorrent-macosx-w.x.y.z.jar` (x86_64 .dylib)
- Linux: `jlibtorrent-linux-w.x.y.z.jar` (x86 and x86_64 .so)
In the case of Android, make sure to put the following 3 jars in your project's `libs` folder (see [FrostWire for Android's](https://github.com/frostwire/frostwire/tree/master/android/libs) as an example):
- `jlibtorrent-w.x.y.z.jar`
- `jlibtorrent-android-arm-w.x.y.z.jar`
- `jlibtorrent-android-x86-w.x.y.z.jar`
If you use ProGuard to obfuscate/minify make sure to add the following statement
`-keep class com.frostwire.jlibtorrent.swig.libtorrent_jni {*;}`
Note that there are multiple version of jlibtorrent for different platforms: `jlibtorrent`, `jlibtorrent-windows`, `jlibtorrent-linux`, `jlibtorrent-macosx` and `jlibtorrent-android-<arch>`. These are all different artifacts.
For examples look at https://github.com/frostwire/frostwire-jlibtorrent/tree/master/src/test/java/com/frostwire/jlibtorrent/demo
Architectures supported:
- Android (armeabi-v7a, arm64-v8a, x86, x86_64)
- Linux (x86, x86_64)
- Windows (x86, x86_64)
- Mac OS X (x86_64)
Building with Travis
====================
You need:
- Setup a travis account at http://travis-ci.org and get familiar with
the service if necessary.
- Open an account with Amazon Web Services (AWS) and get familiar with
S3 (for storage) and IAM (for users).
- Some familiarity with `git` commands.
The process is:
- Create a user in amazon IAM, let's suppose it is `user1`. Download
credentials for the keys.
- Create a bucket in amazon S3, let's suppose it is `jlibtorrent1`.
- Set the permission of the bucket according to your workflow, but at
least the `user1` should have permission to put/upload to the bucket.
See for example this bucket policy:
```json
{
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS":"arn:aws:iam::<user1's ARN here>:user/user1"},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::jlibtorrent1/*"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::jlibtorrent1/*"
}
]
}
```
- Fork the project in github.
- Go to travis and enable the repository.
- Go to 'More options' > 'Settings' > 'Environment Variables' and set the
`S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET` variables using the values in the
credentials file for the user you created and the bucket name you created.
- Clone locally your repo, let's assume to the `jlibtorrent` folder and
checkout the stable branch:
```bash
$ git clone <your fork repo url> jlibtorrent
$ cd jlibtorrent
$ git checkout master
```
- Verify in your travis online if the build already started. The build
could take about 40 minutes, be patient.
- When finished, check your s3 bucket for the binaries.
- To trigger a new build, just make a change or merge new changes from
the stable branch, commit and push.
Building Locally (Mac and Linux)
================================
Building on Travis is something recommended only once you know you're done with your work as builds can take above 30 minutes to finish for all platforms and architectures.
When you're developing and debugging you need faster builds, and these can be performed locally with the help of build scripts in the `swig/` folder.
Thre's a build script for each operating system, for example if you're on macos you can use the `build-macos.sh`, running it without setting things up should tell you about certain environment variables you'll need to set up. To understand the build process we recommend you read your corresponding build script and [`build-utils.shinc`](https://github.com/frostwire/frostwire-jlibtorrent/blob/master/swig/build-utils.shinc)
The hacking and building process might require you to run the `run-swig.sh` script, we usually need to run this script if there are C++ api changes in libtorrent that require adjustments in `libtorrent.i` or `libtorrent.h`, this script will create automatic JNI wrappers in the outer source java folders. You should not run this script unless you know what you're doing.
Then you run the build script for your OS, get to the parent folder and invoke
`gradle build`
The gradle build will look in the swig folder for the JNI shared libraries and it will automatically package the native holding `.jar` files, the final jars will be in the `build/libs` folder.
The windows build script is not finished, for now the windows build is done with travis builds.
Projects using jLibtorrent
==========================
- [FrostWire](http://www.frostwire.com) (both desktop and android editions)
- [TorrentStream-Android](https://github.com/mianharisali/TorrentStream-Android)
- [Simple-Torrent-Android](https://github.com/masterwok/simple-torrent-android)
- [TorrentTunes-Client](https://github.com/dessalines/torrenttunes-client)
- [LibreTorrent](https://github.com/proninyaroslav/libretorrent)
License
========
This software is offered under the MIT License, available [here](License.md).

View File

@ -0,0 +1,234 @@
apply plugin: 'java'
apply plugin: 'signing'
apply plugin: 'maven'
group 'com.frostwire'
archivesBaseName = 'jlibtorrent'
// Just changing version here should be all that's necessary to bump the version on the library
version '1.2.12.0'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
if (!hasProperty('ossrhUsername')) {
ext.ossrhUsername = ''
}
if (!hasProperty('ossrhPassword')) {
ext.ossrhPassword = ''
}
repositories {
mavenCentral()
}
task sourcesJar(type: Jar) {
classifier = 'sources'
from sourceSets.main.allSource
}
task javadoc2(type: Javadoc) {
source = sourceSets.main.allJava
}
task javadocJar(type: Jar, dependsOn: javadoc2) {
classifier = 'javadoc'
from javadoc2.destinationDir
}
task nativeMacOSXJar(type: Zip) {
destinationDir file("$buildDir/libs")
baseName 'jlibtorrent-macosx'
extension 'jar'
from fileTree(dir: 'swig/bin/release/macosx', excludes: ['**/ed25519', '**/src'], include: '**/*libjlibtorrent.dylib')
into 'lib/'
rename(".dylib", "-${version}.dylib")
}
task nativeWindowsJar(type: Zip) {
destinationDir file("$buildDir/libs")
baseName 'jlibtorrent-windows'
extension 'jar'
from fileTree(dir: 'swig/bin/release/windows', excludes: ['**/ed25519', '**/src'], include: '**/*jlibtorrent.dll')
into 'lib/'
rename(".dll", "-${version}.dll")
}
task nativeLinuxJar(type: Zip) {
destinationDir file("$buildDir/libs")
baseName 'jlibtorrent-linux'
extension 'jar'
from fileTree(dir: 'swig/bin/release/linux', excludes: ['**/ed25519', '**/src'], include: '**/*libjlibtorrent.so')
into 'lib/'
rename(".so", "-${version}.so")
}
task nativeAndroidArmJar(type: Zip) {
destinationDir file("$buildDir/libs")
baseName 'jlibtorrent-android-arm'
extension 'jar'
from fileTree(dir: 'swig/bin/release/android', include: 'armeabi-v7a/libjlibtorrent.so')
into 'lib/'
rename(".so", "-${version}.so")
}
task nativeAndroidX86Jar(type: Zip) {
destinationDir file("$buildDir/libs")
baseName 'jlibtorrent-android-x86'
extension 'jar'
from fileTree(dir: 'swig/bin/release/android', include: 'x86/libjlibtorrent.so')
into 'lib/'
rename(".so", "-${version}.so")
}
task nativeAndroidArm64Jar(type: Zip) {
destinationDir file("$buildDir/libs")
baseName 'jlibtorrent-android-arm64'
extension 'jar'
from fileTree(dir: 'swig/bin/release/android', include: 'arm64-v8a/libjlibtorrent.so')
into 'lib/'
rename(".so", "-${version}.so")
}
task nativeAndroidX64Jar(type: Zip) {
destinationDir file("$buildDir/libs")
baseName 'jlibtorrent-android-x86_64'
extension 'jar'
from fileTree(dir: 'swig/bin/release/android', include: 'x86_64/libjlibtorrent.so')
into 'lib/'
rename(".so", "-${version}.so")
}
artifacts {
archives sourcesJar
archives javadocJar
archives nativeMacOSXJar
archives nativeWindowsJar
archives nativeLinuxJar
archives nativeAndroidArmJar
archives nativeAndroidX86Jar
archives nativeAndroidArm64Jar
archives nativeAndroidX64Jar
}
signing {
required { gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
addFilter('jlibtorrent') { artifact, file ->
artifact.name == 'jlibtorrent'
}
addFilter('jlibtorrent-macosx') { artifact, file ->
artifact.name == 'jlibtorrent-macosx'
}
addFilter('jlibtorrent-windows') { artifact, file ->
artifact.name == 'jlibtorrent-windows'
}
addFilter('jlibtorrent-linux') { artifact, file ->
artifact.name == 'jlibtorrent-linux'
}
addFilter('jlibtorrent-android-arm') { artifact, file ->
artifact.name == 'jlibtorrent-android-arm'
}
addFilter('jlibtorrent-android-x86') { artifact, file ->
artifact.name == 'jlibtorrent-android-x86'
}
addFilter('jlibtorrent-android-arm64') { artifact, file ->
artifact.name == 'jlibtorrent-android-arm64'
}
addFilter('jlibtorrent-android-x86_64') { artifact, file ->
artifact.name == 'jlibtorrent-android-x86_64'
}
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
pom('jlibtorrent').withXml {
asNode().children().last() + pomData()
}
pom('jlibtorrent-macosx').withXml {
asNode().children().last() + pomData()
addDependency(asNode())
}
pom('jlibtorrent-windows').withXml {
asNode().children().last() + pomData()
addDependency(asNode())
}
pom('jlibtorrent-linux').withXml {
asNode().children().last() + pomData()
addDependency(asNode())
}
pom('jlibtorrent-android-arm').withXml {
asNode().children().last() + pomData()
addDependency(asNode())
}
pom('jlibtorrent-android-x86').withXml {
asNode().children().last() + pomData()
addDependency(asNode())
}
pom('jlibtorrent-android-arm64').withXml {
asNode().children().last() + pomData()
addDependency(asNode())
}
pom('jlibtorrent-android-x86_64').withXml {
asNode().children().last() + pomData()
addDependency(asNode())
}
}
}
}
def pomData() {
return {
resolveStrategy = Closure.DELEGATE_FIRST
name 'frostwire-jlibtorrent'
description 'A swig Java interface for libtorrent by the makers of FrostWire.'
url 'https://github.com/frostwire/frostwire-jlibtorrent'
scm {
connection 'scm:git:git://github.com/frostwire/frostwire-jlibtorrent.git'
developerConnection 'scm:git:ssh:git@github.com/frostwire/frostwire-jlibtorrent.git'
url 'https://github.com/frostwire/frostwire-jlibtorrent'
}
licenses {
license {
name 'The MIT License'
url 'https://github.com/frostwire/frostwire-jlibtorrent/blob/master/LICENSE.md'
}
}
developers {
developer {
id 'gubatron'
name 'Angel Leon'
email 'gubatron@gmail.com'
}
developer {
id 'aldenml'
name 'Alden Torres'
email 'aldenml@gmail.com'
}
}
}
}
def addDependency(root) {
def dependenciesNode = root.dependencies[0]
if (!dependenciesNode) {
dependenciesNode = root.appendNode('dependencies')
}
def depNode = dependenciesNode.appendNode('dependency')
depNode.appendNode('groupId', group)
depNode.appendNode('artifactId', archivesBaseName)
depNode.appendNode('version', version)
}

View File

@ -0,0 +1,682 @@
1.2.12.0
* lt:fix loading of DHT node ID from previous session on startup
* lt:use getrandom(), when available, and fall back to /dev/urandom
* lt:fix uTP issue acking FIN packets
* lt:validate HTTPS certificates by default (trackers and web seeds)
* lt:load SSL certificates from windows system certificate store, to authenticate trackers
* lt:introduce mitigation for Server Side Request Forgery in tracker and web seed URLs
* lt:fix error handling for pool allocation failure
1.2.11.0
* New TorrentHandle.inSession() [blocking method, do not use in main UI threads]
* New ability for SessionManager to build a paused session
* New SessionManager.start(SessionParams, session_flags_t)
* New SessionHandle.PAUSED (session_flag_t) and corresponding unit tests
* Removed TorrentStatus::State::ALLOCATING enum
* Removed torrent_status.state_t.unused_enum_for_backwards_compatibility_allocating
* libtorrent 1.2.11.0 update (471e772cb7038f1bf5f44c32a09eb42fbb80ee99)
* lt: upgraded to openssl 1.1.1l
* lt: fix issue with moving the session object
* lt: deprecate torrent_status::allocating. This state is no longer used
* lt: fix bug creating torrents with symbolic links
* lt: remove special case to save metadata in resume data unconditionally when added throught magnet link
* lt: fix bugs in mutable-torrent support (reusing identical files from different torrents)
* lt: fix incorrectly inlined move-assignment of file_storage
* lt: add session::paused flag, and the ability to construct a session in paused mode
* lt: fix session-pause causing tracker announces to fail
* lt: fix peer-exchange flags bug
* lt: allow saving resume data before metadata has been downloaded (for magnet links)
* lt: record blocks in the disk queue as downloaded in the resume data
* lt: fix bug in set_piece_deadline() when set in a zero-priority piece
* lt: fix issue in URL parser, causing issues with certain tracker URLs
* lt: use a different error code than host-unreachable, when skipping tracker announces
1.2.10.0
* libtorrent 1.2.10 update (70f1de3f7ec4012aaea420ff150ef0135d397706)
* lt: improve stat_file() performance on Windows
* lt: fix issue with loading invalid torrents with only 0-sized files
* lt: fix to avoid large stack allocations
* lt: add macro TORRENT_CXX11_ABI for clients building with C++14 against
libtorrent build with C++11
* lt: removed deprecated wstring overloads on non-windows systems
* lt: drop dependency on Unicode's ConvertUTF code (which had a license
incompatible with Debian)
* lt: fix bugs exposed on big-endian systems
* lt: fix detection of hard-links not being supported by filesystem
* lt: fixed resume data regression for seeds with prio 0 files
* binaries: compiler upgraded from g++-5 to g++-7
1.2.8.0
* add router.utorrent.com to list of DHT bootstrap nodes
* Android builds with new NDK r21d
* lt: validate UTF-8 encoding of client version strings from peers
* lt: don't time out tracker announces as eagerly while resolving hostnames
* lt: fix NAT-PMP shutdown issue
* lt: improve hostname lookup by merging identical lookups
* lt: fix network route enumeration for large routing tables
* lt: fixed issue where pop_alerts() could return old, invalid alerts
* lt: fix issue when receiving have-all message before the metadata
* lt: don't leave lingering part files handles open
* lt: disallow calling add_piece() during checking
* lt: fix incorrect filename truncation at multi-byte character
* lt: always announce listen port 1 when using a proxy
1.2.7.0
* libtorrent 1.2.7 update (ce0a85f783bec37484776a37fe3662279091ecc5)
* upgraded to boost 1.73.0
* upgraded to openssl 1.1.1g
* lt: fix incorrect filename truncation at multi-byte character
* lt: always announce listen port 1 when using a proxy
* lt: add set_alert_fd in python binding, to supersede set_alert_notify
* lt: fix bug in part files > 2 GiB
* lt: add function to clear the peer list for a torrent
* lt: fix resume data functions to save/restore more torrent flags
* lt: limit number of concurrent HTTP announces
* lt: fix queue position for force_rechecking a torrent that is not auto-managed
* lt: improve rate-based choker documentation, and minor tweak
* lt: undeprecate upnp_ignore_nonrouters (but refering to devices on our subnet)
* lt: increase default tracker timeout
* lt: retry failed socks5 server connections
* lt: allow UPnP lease duration to be changed after device discovery
* lt: fix IPv6 address change detection on Windows
* lt: fix peer timeout logic
* lt: simplify proxy handling. A proxy now overrides listen_interfaces
* lt: fix issues when configured to use a non-default choking algorithm
* lt: fix issue in reading resume data
* lt: revert NXDOMAIN change from 1.2.4
* lt: don't open any listen sockets if listen_interfaces is empty or misconfigured
* lt: fix bug in auto disk cache size logic
* lt: fix issue with outgoing_interfaces setting, where bind() would be called twice
* lt: add build option to disable share-mode
* lt: support validation of HTTPS trackers
* lt: deprecate strict super seeding mode
* lt: make UPnP port-mapping lease duration configurable
* lt: deprecate the bittyrant choking algorithm
* lt: add build option to disable streaming
1.2.6.0
* There was no 1.2.6 release, we waited and jumped all the way to 1.2.7.0
1.2.5.0
* libtorrent 1.2.5 update (0f337b9ce7a1b0fc87f48843933b1c5c4dd5a9ec)
* New SettingsPack::dhtUploadRate(), SettingsPack::dhtUploadRate(int)
* lt:announce port=1 instead of port=0, when there is no listen port
* lt:fix LSD over IPv6
* lt:support TCP_NOTSENT_LOWAT on Linux
* lt:fix correct interface binding of local service discovery multicast
* lt:fix issue with knowing which interfaces to announce to trackers and DHT
* lt:undeprecate settings_pack::dht_upload_rate_limit
1.2.4.0
* libtorrent 1.2.4 update (ad83b1c0eb293b63c69f7879ca6ba2381369f77f)
* Java source compatibility upgraded from 1.7 to 1.8
* lt:fix binding TCP and UDP sockets to the same port, when specifying port 0
* lt:fix announce_to_all_trackers and announce_to_all_tiers behavior
* lt:fix suggest_read_cache setting
* lt:back-off tracker hostname looksups resulting in NXDOMAIN
* lt:lower SOCKS5 UDP keepalive timeout
* lt:fix external IP voting for multi-homed DHT nodes
* lt:deprecate broadcast_lsd setting. Just use multicast
* lt:deprecate upnp_ignore_nonrouters setting
* lt:don't attempt sending event=stopped if event=start never succeeded
* lt:make sure &key= stays consistent between different source IPs (as mandated by BEP7)
* lt:fix binding sockets to outgoing interface
* lt:add new socks5_alert to trouble shoot SOCKS5 proxies
1.2.3.0
* libtorrent 1.2.3 update (b5bf6c3260bd726b0181671625c007be5ad49845)
* using android NDK r20b
* upgraded to boost 1.72.0
* upgraded to openssl 1.1.1d
* swig version 4.0.1 (from 3.0.12)
* lt:fix erroneous event=completed tracker announce when checking files
* lt:promote errors in parsing listen_interfaces to post listen_failed_alert
* lt:fix bug in protocol encryption/obfuscation
* lt:fix buffer overflow in SOCKS5 UDP logic
* lt:fix issue of rapid calls to file_priority() clobbering each other
* lt:clear tracker errors on success
* lt:optimize setting with unlimited unchoke slots
* lt:fixed restoring of trackers, comment, creation date and created-by in resume data
* lt:fix handling of torrents with too large pieces
* lt:fixed division by zero in anti-leech choker
* lt:fixed bug in torrent_info::swap
1.2.2.0
* libtorrent 1.2.2 update (d5b56ca1876dc7b96ef9aac7c7584e1f61d25774)
* using android NDK r20 (released June 2019)
* new local build script for android x86_64 (api level 20)
* updated to boost 1.71.0
* upgraded openssl to 1.1.1c
* compiled with std=c++14 (up from c++11)
* lt:pick contiguous pieces from peers with high download rate
* lt:fix error handling of moving storage to a drive letter that isn't mounted
* lt:fix integer overflow in http parser
* lt:fix integer overflow in chunked http parser
* lt:factor out and unit test parts of the DHT routing table logic
* lt:improve sanitation of symlinks, to support more complex link targets (file_storage::sanitize_symlinks)
* lt:avoid empty dht routing table buckets
* lt:fix dht_stats_alert routing table stats for multi-homed clients
* lt:fix entry assignment from bdecode_node and lazy_entry
* lt:fix use-after-free issue in socket_type
* lt:fix error code messages when building without deprecated functions
* lt:feature to disable DHT, PEX and LSD per torrent
* lt:fix seeding of random number generator on mingw
* lt:fix issue where trackers from magnet links were not included in create_torrent()
* lt:extend the whole_pieces_threshold setting to also request contiguous pieces from fast peers
* lt:fix error handling of moving storage to a drive letter that isn't mounted
* lt:don't leak exceptions out of handler callbacks in resolver
* lt:ensure headers build independently
* lt:fix unit template's mutating operators to give them proper ref qualifiers
1.2.1.0
* using android NDK r19c (released January 2019)
* updated to boost 1.70.0
* lt:make sure session cleanup releases all its references to torrents
* lt:track the mapped port for each NAT mapping transport
* lt:optimize resolve_duplicate_filenames_slow()
* lt:use a more restrictive limit on number of pieces allowed in a torrent
* lt:improve disk I/O logging
* lt:always flush disk I/O job queue before shutdown
* lt:fix typo in validation of reject messages. Make on_choke a bit more defensive
* lt:tighten up validation of dont-have messages
* lt:tighten up validation of reject messages, to ensure consistency of stats counters
* lt:tighten up message size checks
* lt:update symlinks to conform to BEP 47
* lt:source code cleanup, performance and stability
1.2.0.20
* updated to libtorrent RC_1_2 HEAD
* updated to OpenSSL 1.1.1b
* lt:add support for creating symlinks, for torrents with symlinks in them
* lt:allow padfiles of equal size to share the same filename
* lt:fix seed_mode flag
* lt:support numeric suffixes to magnet link parameter names
* lt:added FrostWire's client ID
* lt:don't try to hash empty read in do_uncached_hash
* lt:don't copy a vector into the async_write operation for iovec
* lt:on linux, link against lbdl when using openssl
* lt:use UNC paths pervasively on windows
* lt:source code cleanup, performance and stability
1.2.0.19
* libtorrent release 1.2.0
* using android NDK r18b
* updated to boost 1.69.0
* updated to OpenSSL 1.1.1a
* fixed activeDhtLimit SettingsPack setter
* development: new local build scripts for macosx, android, linux, windows
* lt:only allow cwnd to be reduced so often (utp)
* lt:avoid announcing local ip to private tracker
* lt:don't treat loss of MTU probe packet as a congestion signal (utp)
* lt:make sure we reset the duplicate ack counter every time we don't receive
a duplicate ack (utp)
* lt:remove old (incompatible) sequence number build option (utp)
* lt:don't leave slow-start just because we hit the advertized receive window
* lt:simplify and improve the uTP deferred ACK logic to respond earlier
* lt:improve logic for fast-retransmitting packets on incoming SACK
* lt:improve utp verbose logging a bit and make the parser pull out more
metrics
* lt:restore permissions on directories to 1.1. i.e. rely on umask
* lt:minor fix to invalid_request_alert logging
* lt:add assignment operator to span
* lt:fix %u -> %d format codes
* lt:fix deprecation markup in torrent_status
* lt:tweak heuristic of how to interpret url seeds in multi-file torrents
* lt:added more TORRENT_DEPRECATED_ENUM and deprecated unused aio_max in
settings_pack
* lt:take a string_view in setting_by_name
* lt:fix typo in peer log
* lt:deprecate start_default_features flag, it's only used in deprecated API
* lt:remove verbose peer logging
* lt:make stack_allocator::format_string() grow the buffer for large strings
* lt:move where socket buffers are set up, to happen after the socket is
opened. log errors in the peer's log instead of session and torrent
* lt:add stats counter for the number of outstanding async_accept calls
* lt:fix potential issue where the dht port message is sent before the peer
handshake
* lt:correct %u format code for pieces in printf() calls
* lt:attempt to fix an assert for a newly connected peer that disconnects just
as we receive the metadata
* lt:don't use page aligned disk buffers
* lt:include &ipv4= for private trackers
* lt:add support for &ipv4= tracker argument
* lt:use new bdecoder in ut_metadata class
* lt:fix redundant bytes overflow
* lt:check for self-assignment in bitfield operator=
* lt:initialize bencoded ints with zero when constructed
* lt:exit natpmp::on_reply earlier if we're shutting down
* lt:fix setting ipv6 interface
* lt:a better fix to the ssl port announce bug
* lt:perfect forward async handlers for udp_socket to underlying asio socket
* lt:move the whole add_torrent_params object into save_resume_data_alert
* lt:actually fix the issue with the second tracker announce with port 0
* lt:remove redundant MTU boundary checks
* lt:fix of asio-debugging build in natpmp. resend_request could be called
directly, not only as a handler for an async operation
* lt:fix move_storage with save_path with a trailing slash
* lt:only make snubbed peers invert the piece picking strategy when we're
doing rarest first
* lt:ssl listen port fix and improved tracker announce logging
* lt:properly tear down the disk_io_thread object in set_piece_hashes() when
exiting via an exception
* lt:make throwing versions of read_resume_data
* lt:fix overflow in sliding_average in the case of very high download rates
* lt:renamed debug_notification to connect_notification
* lt:fix issue in udp_socket with unusual socket failure
* lt:utp close-reason use after free fix
* lt:source code cleanup, performance and stability
1.2.0.18
* using android NDK r17c
* updated to boost 1.68.0
* updated to OpenSSL 1.1.1
* expose to java SWIG api the aux::arm_neon_support flag for android
runtime verification
* removed hack of custom getauxval definition
* removed hack of fgetpos fsetpos
* removed hack of mulodi4
* null check protection in EnumNet
* added new constructor to TorrentInfo from byte array
* improved API for AnnounceEntry
* added AnnounceEndpoint java API, it is a lightweight class
* convert ErrorCode to a lightweight class
* convert PeerInfo to a lightweight class, client is UTF-8 decoded
* Vectors.byte_vector2string supports only ASCII and UTF-8
* lt:fix redundant flushes of partfile metadata
* lt:fix overflow in calc_bytes(), fix bug in piece picker accounting of
filtered pad blocks
* lt:improve type-safety of the severity parameter to
peer_connection::disconnect()
* lt:fix seed count when attaching a peer is aborted
* lt:add option to ignore min-interval from tracker, when
force-reannouncing a tracker
* lt:raise default value for active_limit to 500, since it's supposed to be
an upper sanity check limit
* lt:make the print function for entry actually be json-like
* lt:fall back to copy+remove if rename_file fails
* lt:improve handling of filesystems not supporting fallocate()
* lt:improve piece picker performance in tracking pad-blocks
* lt:force proxy no longer disables the DHT
* lt:simplify total_have/have_want/total_want. Make piece_picker track pad
blocks and compute byte-progress at block granularity
* lt:fix issue in self-connection detection introduced with the change to
generate unique peer-ids for each connection.
* lt:fix exporting files to avoid overwriting existing files, before
exporting anything from a parts file, check whether it contains valid data
* lt:improve connect-boost feature, to make new torrents quickly connect
peers
* lt:add a few more stats counters measuring outgoing connection attempts.
simplify session_stats_header_alert by posting it on first call to
post_session_stats() instead of making it gated by the alert_mask
* lt:remove dead code from piece picker
* lt:tweak the auto-cache-size logic to have slightly smaller cache
* lt:fix deprecation of mmap_cache
* lt:add missing increment of on_disk_counter and num_blocks_hashed counters
* lt:apply piece priorities immediately, even though file priority updates
are async. save both file- and piece priorities in fast resume
* lt:simplify natpmp gateway and local address discovery
* lt:fix typo in #if tests for TORRENT_DISABLE_ENCRYPTION
* lt:in torrent_handle::id(), only shift down the pointer by 10 bits
* lt:add piece index range checks on have_piece() and read_piece()
* lt:make metric_type_t an enum class, deprecate the in-class enum values
* lt:only post alerts for newly opened listen sockets, and only attempt to
map ports for newly opened sockets
* lt:remove special handling of uTP peers, uTP connections are no longer
exempt from rate limits by default
* lt:make natpmp deal with address_v6 instead of the bytes_type, and use
write_address instead of memcpy
* lt:fix exporting files from partfile while seeding
* lt:set port in handshake based on source address
* lt:fix Windows "file::preadv" emulation EOF handling
* lt:fix windows async read EOF handling
* lt:fix bug in read/write resume data functions
* lt:dht don't set implied_port for SSL torrents
* lt:dht announce with per-interface listen port
* lt:deprecate (and disable) the force-proxy setting. Instead, always use
the proxy when set, never fall back on circumventing it
* lt:move the file priority vector throught the disk_io_job, to avoid copies
* lt:raise priority of cache_flushed_alert and post it unconditionally when
triggered explicitly by the client
* lt:fix some validation issues in read_resume_data()
* lt:bump the minimum number of hash jobs per thread from 2 to 4
* lt:fix deadlock when loading libtorrent Dll
* lt:fix torrent files prioritization
* lt:fix some unintentional copies (with explicit moves)
* lt:qualify some assignment operators to disallow assignment to temporaries
* lt:fix missing move of file object in part_file
* lt:use more threads when creating torrents
* lt:bump checking_mem_usage default setting
* lt:enable coalesce_reads and coalesce_writes by default on windows
* lt:set the minimum number of checking jobs based on the number of hasher
threads
* lt:fix coalesce read bug
* lt:introduce a fast-path for the hash disk job
* lt:fix race condition in part_file
* lt:fix parts file i/o errors
* lt:fixed sign implicit conversion warnings and loop logic in new enum_net
code
* lt:improve the API for iterating over all files and pieces, with the new
strong index types
* lt:deprecate network-threads setting
* lt:scrape_reply_alert should be high priority, since it's triggered by the
client
* lt:add support for multi-home NAT-PMP and Port Control Protocol (PCP)
* lt:report transport version in NAT-PMP send/receive logs
* lt:return a vector of mapping ids from add_port_mapping
* lt:don't re-map all listen sockets when changing listen_interfaces
* lt:create a natpmp instance for each listen socket
* lt:enum_routes fixes on Linux
* lt:fix scope_id in enum_routes on Windows
* lt:remove UNC prefixes from device names on Windows
* lt:fix netmask of routes on Windows
* lt:deliver notification of alerts being dropped via alerts_dropped_alert
* lt:raise priority of fastresume_rejected_alert
* lt:don't use the partfile for existing files when their priority is 0
* lt:set the hidden attribute when creating the part file
* lt:only start a new accept request on new listen sockets, fix mapping new
listen sockets when re-mapping is not requested
* lt:don't change state to downloading if the torrent is finished
* lt:bump `file_error_alert` priority
* lt:fix address of point-to-point interfaces
* lt:don't enable reuse-address for UDP sockets, as it will always succeed
and not get any incoming packets
* lt:account for partially downloaded pieces when announcing as a seed
* lt:fix bandwidth allocation
* lt:don't attempt to make uTP connections if we don't have any outgoing UDP
sockets
* lt:fix empty outgoing interfaces for UDP sockets
* lt:only allow pinged nodes into the DHT routing table
* lt:introduce a recursive mutex to protect the alert_manager, and hold the
mutex while calling user callbacks and plugin hooks
* lt:revise alert priorities / torrent::on_resume_data_checked issue
* lt:save the number of idle threads locally in
disk_io_thread_pool::thread_active
* lt:fix use after free in flush_range and flush_iovec
* lt:honor torrent abort even on file check error
* lt:use settings_pack::urlseed_wait_retry for default retry with http seeds
* lt:fix storage initialization
* lt:strtoll() returns LLONG_MAX if the input overflows, handle this case
properly in the http parser
* lt:remove the global cache of the current time, just use clock::now()
* lt:deprecated alert::progress_notification alert category, split into
finer grained categories
* lt:disk_io_thread abort_hash_jobs duplicate code refactor
* lt:fix part-file header allocation
* lt:fix potential fd leak in enum_net_interfaces
* lt:don't perform DNS lookups for the DHT bootstrap unless DHT is enabled
* lt:avoid calls to .address() when looking for endpoint protocol
* lt:removed unnecessary loop in request_a_block
* lt:fix changing file priorities while checking interrupts checking
* lt:fix issue where the current tracker would be skipped for the next
tracker in the same tier
* lt:remove redundant check in tracker announce
* lt:minor fixes in utp_socket_impl
* lt:stat files in the disk thread, in default_storage::initialize()
instead of the constructor
* lt:dynamically load getauxval so as to support older android devices
* lt:track whether a file is eligible for using the partfile on a per-file
basis
* lt:define NETLINK_NO_ENOBUFS and IFA_D_DADFAILED if they don't exist
* lt:fix reporting &redundant= in tracker announces
* lt:fix windows build with UNC paths disabled
* lt:fix issue querying block size from torrent before metadata has been
received
* lt:source code cleanup, performance and stability
1.2.0.17
* added EnumNet java API to help query the device network state/status/info
* compiling with -mstackrealign for android x86, some devices have stack
alignment issues
* lt:fix tie-break in duplicate peer connection disconnect logic
* lt:fix issue with SSL tracker connections left in CLOSE_WAIT state
* lt:defer truncating existing files until the first time we write to them
* lt:fix issue when receiving a torrent with 0-sized padfiles as magnet link
* lt:fix issue resuming 1.0.x downloads with a file priority 0
* lt:fix torrent_status::next_announce
* lt:turn piece picker option flags into a proper type
* lt:fix pad-file scalability issue
* lt:made coalesce_reads/coalesce_writes settings take effect on linux
and windows
* lt:source code cleanup, performance and stability
1.2.0.16
* using version in native library names
* updated to boost 1.66.0
* lt:use unique peer_ids per connection
* lt:fix tracker connection bind issue for IPv6 trackers
* lt:fix error handling of merkle torrents
* lt:fix error handling of unsupported hard-links
* lt:raise auto piece size selection limit to 16 MB in create_torrent()
* lt:mark up performance counter operations as noexcept
* lt:fix noexcept marking on entry, and make move assignment
* lt:using make_address instead of deprecated from_string when boost>=1.66
* lt:block_size is a constant, no need in passing it around as a variable
* lt:remove_peer() and attach_peer() error handling
* lt:support forced shutdown/destruction of torrent objects
* lt:improve error handling during session shutdown
* lt:handle serious errors in on_accept_connection handler
* lt:deprecate save_encryption_settings
* lt:handle errors in peer_connection
* lt:support asio handler allocators in deferred_handler
* lt:don't heap-allocate handlers for incoming UDP packets
* lt:make the chunk header parser properly fail at end of buffer, and not
require zero terminated strings
* lt:add getters for peer_class_filter and peer_class_type_filter
* lt:attempt to fix disconnections when torrents enter upload mode due to
failures
* lt:fix local network address mappings
* lt:deprecate status_code from tracker_error_alert
* lt:make torrent_handler::set_priority() to use peer_classes
* lt:improve type safety of plugin interface
* lt:introduce a proper type for pex flags to improve type-safety
* lt:fix reopening of listen sockets when disabling force-proxy
* lt:fix build against boost-1.66, specifically the boost.asio changes
* lt:fix asio debugging
* lt:fix rate limit utp feature
* lt:fix i2p support
* lt:source code cleanup, performance and stability
1.2.0.15
* added java API to download magnet uri
* fixed issue with prioritize_files swig wrapper
* API change in TorrentHandle#saveResumeData, now it pass empty flags
by default
* using android NDK r16b
* updated to OpenSSL 1.1.0g
* lt:fix loading resume data when in seed mode
* lt:fix incorrect use of make_tick_handler
* lt:fix issue with initializing settings on session construction
* lt:implemented support for magnet URI extension, select specific file
indices for download, BEP53
* lt:fix issue with receiving interested before metadata
* lt:generate random keys for use in tracker announces, unique per torrent
and the listen interface
* lt:fix IPv6 tracker announce issue
* lt:don't early move shared_ptr plugin in torrent::add_extension_fun
* lt:fix force-proxy regression (udp sockets would not be opened)
* lt:restore path sanitization behavior of ':'
* lt:make tracker announces happen even if there are no open listen sockets
* lt:fix issue where new listen sockets would not be opened when leaving
force_proxy mode
* lt:make sure the cork destructor doesn't leak exceptions
* lt:stop posting alerts when the session is shutting down, solves some
issues around destruction order
* lt:add API to query whether alerts have been dropped or not
* lt:keep updating aux::time_now() while there are announces, fixes an
infinite loop during shutdown
* lt:source code cleanup, performance and stability
1.2.0.14
* fixed invalid handling of download_priority_t and swig wrapper
1.2.0.13
* enable full SSL support in libtorrent
* compiling with -fvisibility=hidden and -Os
* using android NDK r16 beta2
* updated to boost 1.65.1
* internal session in SessionManager blocks privileged ports and
only allows 80 and 443 for possible web seeds connections
* lt:enable/disable the internal ip notifier with new setting
* lt:fix issue of null m_part_file in default_storage readv/writev
* lt:added reopen_network_sockets method to allow manual reopen of
listen/outgoing sockets
* lt:using NETLINK_NO_ENOBUFS to ignore ENOBUFS in netlink based ip notifier
* lt:add limit of max 50 upnp mappings and recover free global mappings
* lt:creating part file if needed only in set_file_priorities
* lt:fix full allocation failure on APFS
* lt:make parse_magnet_uri return the add_torrent_params instead of
taking an in-out parameter
* lt:pick standard std::aligned_union if using clang
* lt:make disk_buffer_holder know the size of the buffer it holds, to fix
buffer overrun in chained_buffer
* lt:fix infinite loop when parsing certain invalid magnet links
* lt:don't delete pieces from cache with refcount > 0
* lt:don't try to connect to a global address with a local source address
* lt:fix parsing of torrents with certain invalid filenames
* lt:fix leak of peer_class objects (when setting per-torrent rate limits)
* lt:more strict filename sanitation
* lt:improve handling of case where a torrent file has no files in it
* lt:clean up and fix edge cases in update_path_index
* lt:fix issue with the name in single file torrents being sanitized away
* lt:make the chunk header parser a bit more strict and accurate
* lt:fix integer overflow in torrent_info
* lt:fix windows file preallocation issue
* lt:fix integer overflow in whole_pieces_threshold logic
* lt:DHT nodes should only handle requests on their socket
* lt:bump priority of storage_moved_alert and storage_moved_failed_alert
* lt:deprecate lock_files settings
* lt:fix uTP path MTU discovery issue on windows, DF bit was not set
correctly
* lt:select which DHT port to report based on the connection's local
endpoint
* lt:avoid port mapping of local IPv6 addresses
* lt:include endpoint in tracker alerts
* lt:hold an owning reference to storage objects in try_flush_write_blocks
* lt:read_piece: handle failure to allocate piece buffer
* lt:treat unique local IPv6 addresses as local
* lt:source code cleanup, performance and stability
1.2.0.12
* updated to boost 1.65
* storing external address and listen endpoints as strings for better
performance
* improved creation of peers part of magnet links
* lt:fixed netlink based network interface enumeration
* lt:remove support for using a pool allocator for disk buffers
* lt:fix IPv6 tracker support by performing the second announce in
more cases
* lt:fix issue in UTF-8 encoding validation
* lt:fix infinite loop when parsing torrents whose filenames have zeroes
* lt:fix invalid read in parse_int() in bdecode_node()
* lt:don't create web seed connections if the torrent is upload only
* lt:fix issue with very long tracker and web seed URLs
* lt:fix issue where paths were not correctly coalesced when adding files
to file_storage (used more memory than necessary)
* lt:fix issue of force-recheck or seeding from read-only media, torrents
with empty files in them
* lt:fix force-recheck issue (new files would not be picked up)
* lt:source code cleanup, performance and stability
1.2.0.11
* using android NDK r15c
* using -O2 for all architectures
* lt:fix to clearing of piece picker in suggest_read_cache mode
* lt:fix memory issues with listen sockets references
* lt:fix bug where the resume data would fail to load the piece bitmask for
seeds when suggest_cache was enabled
* lt:submit disk jobs in read_piece()
* lt:add reserve entry::to_string() (optimization)
* lt:refactor several flags to torrent_handle::get_flags/set_flags
* lt:added block_uploaded_alert to allow client to track upload activity
* lt:fix inconsistency in file_priorities and override_resume_data behavior
* lt:remove call _strchr (optimization)
* lt:fix bandwidth rate limit calculation
* lt:fix handling of SSL listen sockets
* lt:don't move listen_socket_t when deleting sockets
* lt:avoid executing timed async task if the dht node is already removed
* lt:reject DHT put messages with incorrect bencoding
* lt:fix backwards compatibility issue when loading the torrent info dict
from resume data
* lt:fix regression where paused torrents could not have their queue
position changed
* lt:use netlink to enumerate network interfaces on linux
* lt:fix out-of-bounds read in bdecode
* lt:fix re-check issue after move_storage
* lt:avoid runtime fail with wrong arguments in upnp::update_map
* lt:handle invalid arguments to set_piece_deadline()
* lt:fix that move_storage did not work for torrents without metadata
* lt:fix check for fully allocated file on windows
* lt:defer reconnecting peers to after the second_tick loop
* lt:implemented support for BEP 51
* lt:source code cleanup and stability
1.2.0.10
* added swig interface to announce_endpoint
1.2.0.9
* updated to OpenSSL 1.1.0f
* updated to boost 1.64
* using android NDK r15
* avoid automatic UTF-8 conversion in JNI side when using string_view
* lt:fix bandwidth rate limit calculation
* lt:fix for what appears to be an clang/llvm miscompilation
* lt:only listen on preferred IPv6 addresses on Windows
* lt:when stopping a torrent, never perform a name lookup on the tracker,
only announce to trackers whose IP we already know. This is expected to
make shutdowns not hang
* lt:fix previously faulty fix to enum_routes
* lt:delay 5 seconds before reconnecting socks5 proxy for UDP ASSOCIATE
* lt:fix NAT-PMP crash when removing a mapping at the wrong time
* lt:fix race condition in storage tick handling in disk_io_thread
* lt:keep iterating over endpoints if one is found to be done in
tracker announce loop
* lt:don't abort the existing torrent when attempting to add it again
* lt:fix IPv6 UTP assertion failure, close listen sockets after closing
all connections
* lt:fix branch factor overflow in DHT traversal algorithm
* lt:bind upnp requests to correct local address
* lt:don't combine reuseaddr and exclusive addruse on windows
* lt:save resume data when removing web seeds
* lt:fix proxying of https connections
* lt:fix race condition in disk I/O storage class
* lt:avoid extra sha1_hash memory copy in create_torrent
* lt:fix http connection timeout on multi-homed hosts
* lt:implemented multi-home support
* lt:make DHT bootstrapping more robust by not throwing away nodes
* lt:need_save_resume_data() will no longer return true every 15 minutes
* lt:create a separate DHT node for each listen socket
* lt:avoid connections to trackers when the event is stopped and
stop_tracker_timeout <= 0
* lt:fix storage destruction order issue
* lt:fix memory leak in the disk cache
* lt:magnet links: unescape hash parameter
* lt:fix double free in disk cache
* lt:fix typo in natpmp::start
* lt:remove mutex-release hack in file_pool
* lt:source code cleanup and stability
1.2.0.8
* fixed synchronization issues in SessionManager#stop
1.2.0.7
* added support for ip_notifier in macOS
* added resolver_cache_timeout setting for internal host name resolver
* improved public API
* internal fixes in libtorrent
1.2.0.7-RC3
* start of changelog

View File

@ -0,0 +1,542 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.add_torrent_params;
import com.frostwire.jlibtorrent.swig.error_code;
import com.frostwire.jlibtorrent.swig.int_vector;
import com.frostwire.jlibtorrent.swig.storage_mode_t;
import com.frostwire.jlibtorrent.swig.string_int_pair;
import com.frostwire.jlibtorrent.swig.string_int_pair_vector;
import com.frostwire.jlibtorrent.swig.string_vector;
import com.frostwire.jlibtorrent.swig.tcp_endpoint_vector;
import com.frostwire.jlibtorrent.swig.torrent_flags_t;
import com.frostwire.jlibtorrent.swig.torrent_info;
import java.util.ArrayList;
import java.util.List;
/**
* The {@link AddTorrentParams} is a parameter pack for adding torrents to a
* session. The key fields when adding a torrent are:
* <ul>
* <li>ti - when you have a .torrent file</li>
* <li>url - when you have a magnet link or http URL to the .torrent file</li>
* <li>info_hash - when all you have is an info-hash (this is similar to a magnet link)</li>
* </ul>
* One of those fields need to be set. Another mandatory field is
* {@link #savePath()}. The {@link AddTorrentParams} object is passed into one of the
* {@link SessionHandle#addTorrent(AddTorrentParams, ErrorCode)} overloads or
* {@link SessionHandle#asyncAddTorrent(AddTorrentParams)}.
* <p>
* If you only specify the info-hash, the torrent file will be downloaded
* from peers, which requires them to support the metadata extension. It also
* takes an optional {@link #name()} argument. This may be left empty in case no
* name should be assigned to the torrent. In case it's not, the name is
* used for the torrent as long as it doesn't have metadata.
*
* @author gubatron
* @author aldenml
*/
public final class AddTorrentParams {
private final add_torrent_params p;
/**
* The native object
*
* @param p the native object
*/
public AddTorrentParams(add_torrent_params p) {
this.p = p;
}
/**
* Creates an empty parameters object with the default storage.
*/
public AddTorrentParams() {
this(add_torrent_params.create_instance());
}
/**
* @return the native object
*/
public add_torrent_params swig() {
return p;
}
/**
* Filled in by the constructor. It is used for forward binary compatibility.
*
* @return the version
*/
public int version() {
return p.getVersion();
}
/**
* {@link TorrentInfo} object with the torrent to add.
*
* @return the torrent info or null if not set
*/
public TorrentInfo torrentInfo() {
torrent_info ti = p.ti_ptr();
return ti != null && ti.is_valid() ? new TorrentInfo(ti) : null;
}
/**
* {@link TorrentInfo} object with the torrent to add.
*
* @param ti the torrent info
*/
public void torrentInfo(TorrentInfo ti) {
p.set_ti(ti.swig());
}
/**
* If the torrent doesn't have a tracker, but relies on the DHT to find
* peers, the {@link #trackers(List)} can specify tracker URLs for the
* torrent.
*
* @return the list of trackers
*/
public ArrayList<String> trackers() {
string_vector v = p.get_trackers();
int size = (int) v.size();
ArrayList<String> l = new ArrayList<>();
for (int i = 0; i < size; i++) {
l.add(v.get(i));
}
return l;
}
/**
* If the torrent doesn't have a tracker, but relies on the DHT to find
* peers, this method can specify tracker URLs for the torrent.
*
* @param value the list of trackers
*/
public void trackers(List<String> value) {
string_vector v = new string_vector();
for (String s : value) {
v.push_back(s);
}
p.set_trackers(v);
}
/**
* The tiers the URLs in {@link #trackers()} belong to. Trackers belonging to
* different tiers may be treated differently, as defined by the multi
* tracker extension. This is optional, if not specified trackers are
* assumed to be part of tier 0, or whichever the last tier was as
* iterating over the trackers.
*
* @return the list of trackers tiers
*/
public ArrayList<Integer> trackerTiers() {
int_vector v = p.get_tracker_tiers();
int size = (int) v.size();
ArrayList<Integer> l = new ArrayList<>();
for (int i = 0; i < size; i++) {
l.add(v.get(i));
}
return l;
}
/**
* The tiers the URLs in {@link #trackers()} belong to. Trackers belonging to
* different tiers may be treated differently, as defined by the multi
* tracker extension. This is optional, if not specified trackers are
* assumed to be part of tier 0, or whichever the last tier was as
* iterating over the trackers.
*
* @param value the list of trackers tiers
*/
public void trackerTiers(List<Integer> value) {
int_vector v = new int_vector();
for (Integer t : value) {
v.push_back(t);
}
p.set_tracker_tiers(v);
}
/**
* A list of hostname and port pairs, representing DHT nodes to be added
* to the session (if DHT is enabled). The hostname may be an IP address.
*
* @return the list of DHT nodes
*/
public ArrayList<Pair<String, Integer>> dhtNodes() {
string_int_pair_vector v = p.get_dht_nodes();
int size = (int) v.size();
ArrayList<Pair<String, Integer>> l = new ArrayList<>();
for (int i = 0; i < size; i++) {
string_int_pair n = v.get(i);
l.add(new Pair<>(n.getFirst(), n.getSecond()));
}
return l;
}
/**
* A list of hostname and port pairs, representing DHT nodes to be added
* to the session (if DHT is enabled). The hostname may be an IP address.
*
* @param value the list of DHT nodes
*/
public void dhtNodes(List<Pair<String, Integer>> value) {
string_int_pair_vector v = new string_int_pair_vector();
for (Pair<String, Integer> p : value) {
v.push_back(p.to_string_int_pair());
}
p.set_dht_nodes(v);
}
/**
* @return the name
*/
public String name() {
return p.getName();
}
/**
* @param value the name
*/
public void name(String value) {
p.setName(value);
}
/**
* The path where the torrent is or will be stored. Note that this may
* also be stored in resume data. If you want the save path saved in
* the resume data to be used, you need to set the
* flag_use_resume_save_path flag.
* <p>
* .. note::
* On windows this path (and other paths) are interpreted as UNC
* paths. This means they must use backslashes as directory separators
*
* @return the save path
*/
public String savePath() {
return p.getSave_path();
}
/**
* The path where the torrent is or will be stored. Note that this may
* also be stored in resume data. If you want the save path saved in
* the resume data to be used, you need to set the
* flag_use_resume_save_path flag.
* <p>
* .. note::
* On windows this path (and other paths) are interpreted as UNC
* paths. This means they must use backslashes as directory separators
*
* @param value the save path
*/
public void savePath(String value) {
p.setSave_path(value);
}
/**
* @return the storage mode
* @see StorageMode
*/
public StorageMode storageMode() {
return StorageMode.fromSwig(p.getStorage_mode().swigValue());
}
/**
* @param value the storage mode
* @see StorageMode
*/
public void storageMode(StorageMode value) {
p.setStorage_mode(storage_mode_t.swigToEnum(value.swig()));
}
/**
* The default tracker id to be used when announcing to trackers. By
* default this is empty, and no tracker ID is used, since this is an
* optional argument. If a tracker returns a tracker ID, that ID is used
* instead of this.
*
* @return the trackerid url parameter
*/
public String trackerId() {
return p.getTrackerid();
}
/**
* The default tracker id to be used when announcing to trackers. By
* default this is empty, and no tracker ID is used, since this is an
* optional argument. If a tracker returns a tracker ID, that ID is used
* instead of this.
*
* @param value the trackerid url parameter
*/
public void trackerId(String value) {
p.setTrackerid(value);
}
/**
* Set this to the info hash of the torrent to add in case the info-hash
* is the only known property of the torrent. i.e. you don't have a
* .torrent file nor a magnet link.
*
* @return the info-hash
*/
public Sha1Hash infoHash() {
return new Sha1Hash(p.getInfo_hash());
}
/**
* Set this to the info hash of the torrent to add in case the info-hash
* is the only known property of the torrent. i.e. you don't have a
* .torrent file nor a magnet link.
*
* @param value the info-hash
*/
public void infoHash(Sha1Hash value) {
p.setInfo_hash(value.swig());
}
/**
* @return max uploads limit
*/
public int maxUploads() {
return p.getMax_uploads();
}
/**
* @param value max uploads limit
*/
public void maxUploads(int value) {
p.setMax_uploads(value);
}
/**
* @return max connections limit
*/
public int maxConnections() {
return p.getMax_connections();
}
/**
* @param value max connections limit
*/
public void maxConnections(int value) {
p.setMax_connections(value);
}
/**
* @return upload limit
*/
public int uploadLimit() {
return p.getUpload_limit();
}
/**
* @param value upload limit
*/
public void uploadLimit(int value) {
p.setUpload_limit(value);
}
/**
* @return download limit
*/
public int downloadLimit() {
return p.getDownload_limit();
}
/**
* @param value download limit
*/
public void downloadLimit(int value) {
p.setDownload_limit(value);
}
/**
* Flags controlling aspects of this torrent and how it's added.
*
* @return the flags
*/
public torrent_flags_t flags() {
return p.getFlags();
}
/**
* Flags controlling aspects of this torrent and how it's added.
*
* @param flags the flags
*/
public void flags(torrent_flags_t flags) {
p.setFlags(flags);
}
/**
* Url seeds to be added to the torrent (`BEP 17`_).
*
* @return the url seeds
*/
public ArrayList<String> urlSeeds() {
string_vector v = p.get_url_seeds();
int size = (int) v.size();
ArrayList<String> l = new ArrayList<>();
for (int i = 0; i < size; i++) {
l.add(v.get(i));
}
return l;
}
/**
* Url seeds to be added to the torrent (`BEP 17`_).
*
* @param value the url seeds
*/
public void urlSeeds(List<String> value) {
string_vector v = new string_vector();
for (String s : value) {
v.push_back(s);
}
p.set_url_seeds(v);
}
/**
* Can be set to control the initial file priorities when adding a
* torrent. The semantics are the same as for
* {@link TorrentHandle#prioritizeFiles(Priority[])}.
*
* @param priorities the priorities
*/
public void filePriorities(Priority[] priorities) {
p.set_file_priorities2(Priority.array2byte_vector(priorities));
}
/**
* This sets the priorities for each individual piece in the torrent. Each
* element in the vector represent the piece with the same index. If you
* set both file- and piece priorities, file priorities will take
* precedence.
*
* @param priorities the priorities
*/
public void piecePriorities(Priority[] priorities) {
p.set_piece_priorities2(Priority.array2byte_vector(priorities));
}
/**
* Peers to add to the torrent, to be tried to be connected to as
* bittorrent peers.
*
* @return the peers list
*/
public ArrayList<TcpEndpoint> peers() {
tcp_endpoint_vector v = p.get_peers();
int size = (int) v.size();
ArrayList<TcpEndpoint> l = new ArrayList<>();
for (int i = 0; i < size; i++) {
l.add(new TcpEndpoint(v.get(i)));
}
return l;
}
/**
* Peers to add to the torrent, to be tried to be connected to as
* bittorrent peers.
*
* @param value the peers list
*/
public void peers(List<TcpEndpoint> value) {
tcp_endpoint_vector v = new tcp_endpoint_vector();
for (TcpEndpoint endp : value) {
v.push_back(endp.swig());
}
p.set_peers(v);
}
/**
* Peers banned from this torrent. The will not be connected to.
*
* @return the peers list
*/
public ArrayList<TcpEndpoint> bannedPeers() {
tcp_endpoint_vector v = p.get_banned_peers();
int size = (int) v.size();
ArrayList<TcpEndpoint> l = new ArrayList<>();
for (int i = 0; i < size; i++) {
l.add(new TcpEndpoint(v.get(i)));
}
return l;
}
/**
* Peers banned from this torrent. The will not be connected to.
*
* @param value the peers list
*/
public void bannedPeers(List<TcpEndpoint> value) {
tcp_endpoint_vector v = new tcp_endpoint_vector();
for (TcpEndpoint endp : value) {
v.push_back(endp.swig());
}
p.set_banned_peers(v);
}
/**
* @return an instance with the default storage
*/
public static AddTorrentParams createInstance() {
return new AddTorrentParams(add_torrent_params.create_instance());
}
/**
* @return an instance with a disabled storage
*/
public static AddTorrentParams createInstanceDisabledStorage() {
return new AddTorrentParams(add_torrent_params.create_instance_disabled_storage());
}
/**
* @return an instance with a zero storage
*/
public static AddTorrentParams createInstanceZeroStorage() {
return new AddTorrentParams(add_torrent_params.create_instance_zero_storage());
}
/**
* Helper function to parse a magnet uri and fill the parameters.
*
* @param uri the magnet uri
* @return the params object filled with the data from the magnet
*/
public static AddTorrentParams parseMagnetUri(String uri) {
error_code ec = new error_code();
add_torrent_params params = add_torrent_params.parse_magnet_uri(uri, ec);
if (ec.value() != 0) {
throw new IllegalArgumentException("Invalid magnet uri: " + ec.message());
}
return new AddTorrentParams(params);
}
}

View File

@ -0,0 +1,128 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.address;
import com.frostwire.jlibtorrent.swig.error_code;
/**
* @author gubatron
* @author aldenml
*/
public final class Address implements Comparable<Address>, Cloneable {
private final address addr;
/**
* @param addr the native object
*/
public Address(address addr) {
this.addr = addr;
}
/**
* Create an address from an IPv4 address string in dotted decimal form,
* or from an IPv6 address in hexadecimal notation.
*
* @param ip the ip string representation
*/
public Address(String ip) {
error_code ec = new error_code();
this.addr = address.from_string(ip, ec);
if (ec.value() != 0) {
throw new IllegalArgumentException(ec.message());
}
}
/**
*
*/
public Address() {
this(new address());
}
/**
* @return native object
*/
public address swig() {
return addr;
}
/**
* Get whether the address is an IP version 4 address.
*
* @return if it's an IPv4 address
*/
public boolean isV4() {
return addr.is_v4();
}
/**
* Get whether the address is an IP version 6 address.
*
* @return if it's an IPv6 address
*/
public boolean isV6() {
return addr.is_v6();
}
/**
* Determine whether the address is a loopback address.
*
* @return if it's a loopback address
*/
public boolean isLoopback() {
return addr.is_loopback();
}
/**
* Determine whether the address is unspecified.
*
* @return if it's an unspecified address
*/
public boolean isUnspecified() {
return addr.is_unspecified();
}
/**
* Determine whether the address is a multicast address.
*
* @return if it's an multicast address
*/
public boolean isMulticast() {
return addr.is_multicast();
}
/**
* Compare addresses for ordering.
*
* @param o the other address
* @return -1, 0 or 1
*/
@Override
public int compareTo(Address o) {
return address.compare(this.addr, o.addr);
}
/**
* Get the address as a string in dotted decimal format.
*
* @return string representation
*/
@Override
public String toString() {
return toString(addr);
}
@Override
public Address clone() {
return new Address(new address(addr));
}
static String toString(address a) {
error_code ec = new error_code();
String s = a.to_string(ec);
if (ec.value() != 0) {
s = "<invalid address>";
}
return s;
}
}

View File

@ -0,0 +1,23 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.alerts.Alert;
/**
* @author gubatron
* @author aldenml
*/
public interface AlertListener {
/**
* List of alert types filtered by this listener.
* Return `null` if you intend to listen to all alerts.
*
* @return the types filter
*/
int[] types();
/**
* @param alert the alert
*/
void alert(Alert<?> alert);
}

View File

@ -0,0 +1,64 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.alerts.Alert;
/**
* @author gubatron
* @author aldenml
*/
final class AlertMulticaster implements AlertListener {
private final AlertListener a;
private final AlertListener b;
public AlertMulticaster(AlertListener a, AlertListener b) {
this.a = a;
this.b = b;
}
@Override
public int[] types() {
return null;
}
@Override
public void alert(Alert<?> alert) {
a.alert(alert);
b.alert(alert);
}
public static AlertListener add(AlertListener a, AlertListener b) {
return addInternal(a, b);
}
public static AlertListener remove(AlertListener l, AlertListener oldl) {
return removeInternal(l, oldl);
}
private AlertListener remove(AlertListener oldl) {
if (oldl == a) return b;
if (oldl == b) return a;
AlertListener a2 = removeInternal(a, oldl);
AlertListener b2 = removeInternal(b, oldl);
if (a2 == a && b2 == b) {
return this; // it's not here
}
return addInternal(a2, b2);
}
private static AlertListener addInternal(AlertListener a, AlertListener b) {
if (a == null) return b;
if (b == null) return a;
return new AlertMulticaster(a, b);
}
private static AlertListener removeInternal(AlertListener l, AlertListener oldl) {
if (l == oldl || l == null) {
return null;
} else if (l instanceof AlertMulticaster) {
return ((AlertMulticaster) l).remove(oldl);
} else {
return l; // it's not here
}
}
}

View File

@ -0,0 +1,166 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.announce_endpoint;
/**
* Announces are sent to each tracker using every listen socket, this class
* holds information about one listen socket for one tracker.
* <p>
* This class is a lightweight version of the native {@link announce_endpoint},
* and only carries a subset of all the information. However, it's completely
* open for custom use or optimization to accommodate client necessities.
*
* @author gubatron
* @author aldenml
*/
public class AnnounceEndpoint {
protected String message;
protected ErrorCode lastError;
protected String localEndpoint;
protected long nextAnnounce;
protected long minAnnounce;
protected int scrapeIncomplete;
protected int scrapeComplete;
protected int scrapeDownloaded;
protected int fails;
protected boolean updating;
protected boolean isWorking;
/**
* @param e the native object
*/
public AnnounceEndpoint(announce_endpoint e) {
init(e);
}
/**
* If this tracker has returned an error or warning message
* that message is stored here.
*
* @return the error or warning message
*/
public String message() {
return message;
}
/**
* If this tracker failed the last time it was contacted
* this error code specifies what error occurred.
*
* @return the last error code
*/
public ErrorCode lastError() {
return lastError;
}
/**
* The local endpoint of the listen interface associated with this endpoint.
*
* @return the local endpoint
*/
public String localEndpoint() {
return localEndpoint;
}
/**
* The time of next tracker announce in milliseconds.
*
* @return the time of next tracker announce in milliseconds
*/
public long nextAnnounce() {
return nextAnnounce;
}
/**
* No announces before this time.
*/
public long minAnnounce() {
return minAnnounce;
}
/**
* This is either -1 or the scrape information this tracker last
* responded with. Incomplete is the current number of downloaders in
* the swarm.
* <p>
* If this tracker has returned scrape data, these fields are filled in
* with valid numbers. Otherwise they are set to -1.
*
* @return the number of current downloaders
*/
public int scrapeIncomplete() {
return scrapeIncomplete;
}
/**
* This is either -1 or the scrape information this tracker last
* responded with. Complete is the current number of seeds in the swarm.
* <p>
* If this tracker has returned scrape data, these fields are filled in
* with valid numbers. Otherwise they are set to -1.
*
* @return the current number of seeds
*/
public int scrapeComplete() {
return scrapeComplete;
}
/**
* This is either -1 or the scrape information this tracker last
* responded with. Downloaded is the cumulative number of completed
* downloads of this torrent, since the beginning of time
* (from this tracker's point of view).
* <p>
* If this tracker has returned scrape data, these fields are filled in
* with valid numbers. Otherwise they are set to -1.
*
* @return the cumulative number of completed downloads
*/
public int scrapeDownloaded() {
return scrapeDownloaded;
}
/**
* The number of times in a row we have failed to announce to this tracker.
*
* @return the number of fails
*/
public int fails() {
return fails;
}
/**
* True while we're waiting for a response from the tracker.
*/
public boolean updating() {
return updating;
}
/**
* Returns true if the last time we tried to announce to this
* tracker succeeded, or if we haven't tried yet.
*/
public boolean isWorking() {
return isWorking;
}
/**
* NOTE: use this with care and only if necessary.
*
* @param e the native object
*/
protected void init(announce_endpoint e) {
message = Vectors.byte_vector2ascii(e.get_message());
lastError = new ErrorCode(e.getLast_error());
localEndpoint = new TcpEndpoint(e.getLocal_endpoint()).toString();
nextAnnounce = e.get_next_announce();
minAnnounce = e.get_min_announce();
scrapeIncomplete = e.getScrape_incomplete();
scrapeComplete = e.getScrape_complete();
scrapeDownloaded = e.getScrape_downloaded();
fails = e.getFails();
updating = e.getUpdating();
isWorking = e.is_working();
}
}

View File

@ -0,0 +1,128 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.announce_endpoint_vector;
import com.frostwire.jlibtorrent.swig.announce_entry;
import java.util.ArrayList;
import java.util.List;
/**
* This class holds information about one bittorrent tracker, as it
* relates to a specific torrent.
*
* @author gubatron
* @author aldenml
*/
public final class AnnounceEntry {
private final announce_entry e;
/**
* @param e the native object
*/
public AnnounceEntry(announce_entry e) {
this.e = e;
}
/**
* Constructs a tracker announce entry with {@code u} as the URL.
*
* @param url the tracker url
*/
public AnnounceEntry(String url) {
this(new announce_entry(Vectors.ascii2byte_vector(url)));
}
/**
* @return the native object
*/
public announce_entry swig() {
return e;
}
public List<AnnounceEndpoint> endpoints() {
announce_endpoint_vector v = e.getEndpoints();
int size = (int) v.size();
ArrayList<AnnounceEndpoint> l = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
l.add(new AnnounceEndpoint(v.get(i)));
}
return l;
}
/**
* Tracker URL as it appeared in the torrent file.
*
* @return the tracker url
*/
public String url() {
return Vectors.byte_vector2ascii(e.get_url());
}
public void url(String value) {
e.set_url(Vectors.ascii2byte_vector(value));
}
/**
* The current {@code &trackerid=} argument passed to the tracker.
* this is optional and is normally empty (in which case no
* trackerid is sent).
*
* @return the trackerid url argument
*/
public String trackerId() {
return Vectors.byte_vector2ascii(e.get_trackerid());
}
public void trackerId(String value) {
e.set_trackerid(Vectors.ascii2byte_vector(value));
}
/**
* The tier this tracker belongs to.
*
* @return the tier number
*/
public int tier() {
return e.getTier();
}
public void tier(short value) {
e.setTier(value);
}
/**
* The max number of failures to announce to this tracker in
* a row, before this tracker is not used anymore. 0 means unlimited.
*
* @return the max number of failures allowed
*/
public int failLimit() {
return e.getFail_limit();
}
public void failLimit(short value) {
e.setFail_limit(value);
}
/**
* A bitmask specifying which sources we got this tracker from.
*
* @return the source bitmask
*/
public int source() {
return e.getSource();
}
/**
* Set to true the first time we receive a valid response
* from this tracker.
*
* @return if the tracker has received a valid response
*/
public boolean isVerified() {
return e.getVerified();
}
}

View File

@ -0,0 +1,76 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.bdecode_node;
import com.frostwire.jlibtorrent.swig.byte_vector;
import com.frostwire.jlibtorrent.swig.error_code;
/**
* @author gubatron
* @author aldenml
*/
public final class BDecodeNode {
private final bdecode_node n;
private final byte_vector buffer;
/**
* @param n the native object
*/
public BDecodeNode(bdecode_node n) {
this(n, null);
}
/**
* Used to keep the buffer reference around.
*
* @param n the native object
* @param buffer the buffer backing up the native object
*/
public BDecodeNode(bdecode_node n, byte_vector buffer) {
this.n = n;
this.buffer = buffer;
}
/**
* @return the native object
*/
public bdecode_node swig() {
return n;
}
/**
* This methods returns the internal buffer or null
* if it was constructed without one.
* <p>
* This also prevent premature garbage collection in case
* the node was created from a constructed buffer.
*
* @return the pinned buffer
*/
public byte_vector buffer() {
return buffer;
}
/**
* A JSON style string representation
*
* @return the string representation
*/
@Override
public String toString() {
return bdecode_node.to_string(n, false, 2);
}
public static BDecodeNode bdecode(byte[] data) {
byte_vector buffer = Vectors.bytes2byte_vector(data);
bdecode_node n = new bdecode_node();
error_code ec = new error_code();
int ret = bdecode_node.bdecode(buffer, n, ec);
if (ret == 0) {
return new BDecodeNode(n, buffer);
} else {
throw new IllegalArgumentException("Can't decode data: " + ec.message());
}
}
}

View File

@ -0,0 +1,135 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.block_info;
/**
* Holds the state of a block in a piece. Who we requested
* it from and how far along we are at downloading it.
*
* @author gubatron
* @author aldenml
*/
public final class BlockInfo {
private final block_info b;
/**
* @param b the native object
*/
public BlockInfo(block_info b) {
this.b = b;
}
/**
* @return the native object
*/
public block_info swig() {
return b;
}
/**
* The peer is the ip address of the peer this block was downloaded from.
*
* @return the peer tcp endpoint
*/
public TcpEndpoint peer() {
return new TcpEndpoint(b.peer());
}
/**
* The number of bytes that have been received for this block.
*
* @return the number of bytes received
*/
public int bytesProgress() {
return (int) b.getBytes_progress();
}
/**
* The total number of bytes in this block.
*
* @return otal number of bytes
*/
public int blockSize() {
return (int) b.getBlock_size();
}
/**
* The state this block is in.
*
* @return the block's state
*/
public BlockState state() {
return BlockState.fromSwig((int) b.getState());
}
/**
* The number of peers that is currently requesting this block. Typically
* this is 0 or 1, but at the end of the torrent blocks may be requested
* by more peers in parallel to speed things up.
*
* @return number of peers
*/
public int numPeers() {
return (int) b.getNum_peers();
}
/**
* This is the enum used for {@link #state()}.
*/
public enum BlockState {
/**
* This block has not been downloaded or requested form any peer.
*/
NONE(block_info.block_state_t.none.swigValue()),
/**
* The block has been requested, but not completely downloaded yet.
*/
REQUESTED(block_info.block_state_t.requested.swigValue()),
/**
* The block has been downloaded and is currently queued for being
* written to disk.
*/
WRITING(block_info.block_state_t.writing.swigValue()),
/**
* The block has been written to disk.
*/
FINISHED(block_info.block_state_t.finished.swigValue()),
/**
*
*/
UNKNOWN(-1);
BlockState(int swigValue) {
this.swigValue = swigValue;
}
private final int swigValue;
/**
* @return the native value
*/
public int swig() {
return swigValue;
}
/**
* @param swigValue the native value
* @return the state
*/
public static BlockState fromSwig(int swigValue) {
BlockState[] enumValues = BlockState.class.getEnumConstants();
for (BlockState ev : enumValues) {
if (ev.swig() == swigValue) {
return ev;
}
}
return UNKNOWN;
}
}
}

View File

@ -0,0 +1,130 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.dht_lookup;
/**
* Holds statistics about a current dht_lookup operation.
* a DHT lookup is the traversal of nodes, looking up a
* set of target nodes in the DHT for retrieving and possibly
* storing information in the DHT
*
* @author gubatron
* @author aldenml
*/
public final class DhtLookup {
private final dht_lookup l;
/**
* internal use
*
* @param l
*/
public DhtLookup(dht_lookup l) {
this.l = l;
}
/**
* The native object.
*
* @return the native object
*/
public dht_lookup swig() {
return l;
}
/**
* string literal indicating which kind of lookup this is.
*
* @return
*/
public String type() {
return l.get_type();
}
/**
* the number of outstanding request to individual nodes
* this lookup has right now.
*
* @return
*/
public int outstandingRequests() {
return l.getOutstanding_requests();
}
/**
* the total number of requests that have timed out so far
* for this lookup.
*
* @return
*/
public int timeouts() {
return l.getTimeouts();
}
/**
* the total number of responses we have received for this
* lookup so far for this lookup.
*
* @return
*/
public int responses() {
return l.getResponses();
}
/**
* the branch factor for this lookup. This is the number of
* nodes we keep outstanding requests to in parallel by default.
* when nodes time out we may increase this.
*
* @return
*/
public int branchFactor() {
return l.getBranch_factor();
}
/**
* the number of nodes left that could be queries for this
* lookup. Many of these are likely to be part of the trail
* while performing the lookup and would never end up actually
* being queried.
*
* @return
*/
public int nodesLeft() {
return l.getNodes_left();
}
/**
* the number of seconds ago the
* last message was sent that's still
* outstanding.
*
* @return
*/
public int lastSent() {
return l.getLast_sent();
}
/**
* the number of outstanding requests
* that have exceeded the short timeout
* and are considered timed out in the
* sense that they increased the branch
* factor.
*
* @return
*/
public int firstTimeout() {
return l.getFirst_timeout();
}
/**
* The node-id or info-hash target for this lookup.
*
* @return the target
*/
public Sha1Hash target() {
return new Sha1Hash(l.getTarget());
}
}

View File

@ -0,0 +1,49 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.dht_routing_bucket;
/**
* Hold information about a single DHT routing table bucket
*
* @author gubatron
* @author aldenml
*/
public final class DhtRoutingBucket {
private final dht_routing_bucket t;
public DhtRoutingBucket(dht_routing_bucket t) {
this.t = t;
}
public dht_routing_bucket swig() {
return t;
}
/**
* The total number of nodes in the routing table.
*
* @return
*/
public int numNodes() {
return t.getNum_nodes();
}
/**
* The total number of replacement nodes in the routing table.
*
* @return
*/
public int numReplacements() {
return t.getNum_replacements();
}
/**
* Number of seconds since last activity.
*
* @return
*/
public int lastActive() {
return t.getLast_active();
}
}

View File

@ -0,0 +1,452 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.dht_settings;
/**
* Structure used to hold configuration options for the DHT.
*
* @author gubatron
* @author aldenml
*/
public final class DhtSettings {
private final dht_settings s;
public DhtSettings(dht_settings s) {
this.s = s;
}
public DhtSettings() {
this(new dht_settings());
}
public dht_settings swig() {
return s;
}
/**
* the maximum number of peers to send in a reply to ``get_peers``.
*
* @return
*/
public int maxPeersReply() {
return s.getMax_peers_reply();
}
/**
* the maximum number of peers to send in a reply to ``get_peers``.
*
* @param value
*/
public void maxPeersReply(int value) {
s.setMax_peers_reply(value);
}
/**
* the number of concurrent search request the node will send when
* announcing and refreshing the routing table. This parameter is called
* alpha in the kademlia paper.
*
* @return
*/
public int getSearchBranching() {
return s.getSearch_branching();
}
/**
* the number of concurrent search request the node will send when
* announcing and refreshing the routing table. This parameter is called
* alpha in the kademlia paper.
*
* @param value
*/
public void setSearchBranching(int value) {
s.setSearch_branching(value);
}
/**
* the maximum number of failed tries to contact a node before it is
* removed from the routing table. If there are known working nodes that
* are ready to replace a failing node, it will be replaced immediately,
* this limit is only used to clear out nodes that don't have any node
* that can replace them.
*
* @return
*/
public int getMaxFailCount() {
return s.getMax_fail_count();
}
/**
* the maximum number of failed tries to contact a node before it is
* removed from the routing table. If there are known working nodes that
* are ready to replace a failing node, it will be replaced immediately,
* this limit is only used to clear out nodes that don't have any node
* that can replace them.
*
* @param value
*/
public void setMaxFailCount(int value) {
s.setMax_fail_count(value);
}
/**
* the total number of torrents to track from the DHT. This is simply an
* upper limit to make sure malicious DHT nodes cannot make us allocate
* an unbounded amount of memory.
*
* @return
*/
public int maxTorrents() {
return s.getMax_torrents();
}
/**
* the total number of torrents to track from the DHT. This is simply an
* upper limit to make sure malicious DHT nodes cannot make us allocate
* an unbounded amount of memory.
*
* @param value
*/
public void maxTorrents(int value) {
s.setMax_torrents(value);
}
/**
* max number of items the DHT will store.
*
* @return
*/
public int maxDhtItems() {
return s.getMax_dht_items();
}
/**
* max number of items the DHT will store.
*
* @param value
*/
public void maxDhtItems(int value) {
s.setMax_dht_items(value);
}
/**
* The max number of peers to store per torrent (for the DHT).
*
* @return
*/
public int maxPeers() {
return s.getMax_peers();
}
/**
* The max number of peers to store per torrent (for the DHT).
*
* @param value
*/
public void maxPeers(int value) {
s.setMax_peers(value);
}
/**
* the max number of torrents to return in a torrent search query to the
* DHT.
*
* @return
*/
public int getMaxTorrentSearchReply() {
return s.getMax_torrent_search_reply();
}
/**
* the max number of torrents to return in a torrent search query to the
* DHT.
*
* @param value
*/
public void setMaxTorrentSearchReply(int value) {
s.setMax_torrent_search_reply(value);
}
/**
* determines if the routing table entries should restrict entries to one
* per IP. This defaults to true, which helps mitigate some attacks on
* the DHT. It prevents adding multiple nodes with IPs with a very close
* CIDR distance.
* <p>
* when set, nodes whose IP address that's in the same /24 (or /64 for
* IPv6) range in the same routing table bucket. This is an attempt to
* mitigate node ID spoofing attacks also restrict any IP to only have a
* single entry in the whole routing table.
*
* @return
*/
public boolean isRestrictRoutingIPs() {
return s.getRestrict_routing_ips();
}
/**
* determines if the routing table entries should restrict entries to one
* per IP. This defaults to true, which helps mitigate some attacks on
* the DHT. It prevents adding multiple nodes with IPs with a very close
* CIDR distance.
* <p>
* when set, nodes whose IP address that's in the same /24 (or /64 for
* IPv6) range in the same routing table bucket. This is an attempt to
* mitigate node ID spoofing attacks also restrict any IP to only have a
* single entry in the whole routing table.
*
* @param value
*/
public void setRestrictRoutingIPs(boolean value) {
s.setRestrict_routing_ips(value);
}
/**
* determines if DHT searches should prevent adding nodes with IPs with
* very close CIDR distance. This also defaults to true and helps
* mitigate certain attacks on the DHT.
*
* @return
*/
public boolean isRestrictSearchIPs() {
return s.getRestrict_search_ips();
}
/**
* determines if DHT searches should prevent adding nodes with IPs with
* very close CIDR distance. This also defaults to true and helps
* mitigate certain attacks on the DHT.
*
* @param value
*/
public void setRestrictSearchIPs(boolean value) {
s.setRestrict_search_ips(value);
}
/**
* makes the first buckets in the DHT routing table fit 128, 64, 32 and
* 16 nodes respectively, as opposed to the standard size of 8. All other
* buckets have size 8 still.
*
* @return
*/
public boolean isExtendedRoutingTable() {
return s.getExtended_routing_table();
}
/**
* makes the first buckets in the DHT routing table fit 128, 64, 32 and
* 16 nodes respectively, as opposed to the standard size of 8. All other
* buckets have size 8 still.
*
* @param value
*/
public void setExtendedRoutingTable(boolean value) {
s.setExtended_routing_table(value);
}
/**
* slightly changes the lookup behavior in terms of how many outstanding
* requests we keep. Instead of having branch factor be a hard limit, we
* always keep *branch factor* outstanding requests to the closest nodes.
* i.e. every time we get results back with closer nodes, we query them
* right away. It lowers the lookup times at the cost of more outstanding
* queries.
*
* @return
*/
public boolean isAggressiveLookups() {
return s.getAggressive_lookups();
}
/**
* slightly changes the lookup behavior in terms of how many outstanding
* requests we keep. Instead of having branch factor be a hard limit, we
* always keep *branch factor* outstanding requests to the closest nodes.
* i.e. every time we get results back with closer nodes, we query them
* right away. It lowers the lookup times at the cost of more outstanding
* queries.
*
* @param value
*/
public void getAggressiveLookups(boolean value) {
s.setAggressive_lookups(value);
}
/**
* when set, perform lookups in a way that is slightly more expensive,
* but which minimizes the amount of information leaked about you.
*
* @return
*/
public boolean isPrivacyLookups() {
return s.getPrivacy_lookups();
}
/**
* when set, perform lookups in a way that is slightly more expensive,
* but which minimizes the amount of information leaked about you.
*
* @param value
*/
public void setPrivacyLookups(boolean value) {
s.setPrivacy_lookups(value);
}
/**
* when set, node's whose IDs that are not correctly generated based on
* its external IP are ignored. When a query arrives from such node, an
* error message is returned with a message saying "invalid node ID".
*
* @return
*/
public boolean isEnforceNodeId() {
return s.getEnforce_node_id();
}
/**
* when set, node's whose IDs that are not correctly generated based on
* its external IP are ignored. When a query arrives from such node, an
* error message is returned with a message saying "invalid node ID".
*
* @param value
*/
public void setEnforceNodeId(boolean value) {
s.setEnforce_node_id(value);
}
/**
* ignore DHT messages from parts of the internet we wouldn't expect to
* see any traffic from
*
* @return
*/
public boolean isIgnoreDarkInternet() {
return s.getIgnore_dark_internet();
}
/**
* ignore DHT messages from parts of the internet we wouldn't expect to
* see any traffic from
*
* @param value
*/
public void setIgnoreDarkInternet(boolean value) {
s.setIgnore_dark_internet(value);
}
/**
* The number of seconds a DHT node is banned if it exceeds the rate
* limit. The rate limit is averaged over 10 seconds to allow for bursts
* above the limit.
*
* @return
*/
public int blockTimeout() {
return s.getBlock_timeout();
}
/**
* The number of seconds a DHT node is banned if it exceeds the rate
* limit. The rate limit is averaged over 10 seconds to allow for bursts
* above the limit.
*
* @param value
*/
public void blockTimeout(int value) {
s.setBlock_timeout(value);
}
/**
* The max number of packets per second a DHT node is allowed to send
* without getting banned.
*
* @return
*/
public int blockRatelimit() {
return s.getBlock_ratelimit();
}
/**
* The max number of packets per second a DHT node is allowed to send
* without getting banned.
*
* @param value
*/
public void blockRatelimit(int value) {
s.setBlock_ratelimit(value);
}
/**
* When set, the other nodes won't keep this node in their routing
* tables, it's meant for low-power and/or ephemeral devices that
* cannot support the DHT, it is also useful for mobile devices which
* are sensitive to network traffic and battery life.
* this node no longer responds to 'query' messages, and will place a
* 'ro' key (value = 1) in the top-level message dictionary of outgoing
* query messages.
*
* @return
*/
public boolean readOnly() {
return s.getRead_only();
}
/**
* When set, the other nodes won't keep this node in their routing
* tables, it's meant for low-power and/or ephemeral devices that
* cannot support the DHT, it is also useful for mobile devices which
* are sensitive to network traffic and battery life.
* this node no longer responds to 'query' messages, and will place a
* 'ro' key (value = 1) in the top-level message dictionary of outgoing
* query messages.
*
* @param value
*/
public void readOnly(boolean value) {
s.setRead_only(value);
}
/**
* The number of seconds a immutable/mutable item will be expired.
* default is 0, means never expires.
*
* @return
*/
public int itemLifetime() {
return s.getItem_lifetime();
}
/**
* The number of seconds a immutable/mutable item will be expired.
* default is 0, means never expires.
*
* @param value
*/
public void itemLifetime(int value) {
s.setItem_lifetime(value);
}
/**
* The number of bytes per second (on average) the DHT is allowed to send.
* If the incoming requests causes to many bytes to be sent in responses,
* incoming requests will be dropped until the quota has been replenished.
*
* @return
*/
public int uploadRateLimit() {
return s.getUpload_rate_limit();
}
/**
* The number of bytes per second (on average) the DHT is allowed to send.
* If the incoming requests causes to many bytes to be sent in responses,
* incoming requests will be dropped until the quota has been replenished.
*
* @param value
*/
public void uploadRateLimit(int value) {
s.setUpload_rate_limit(value);
}
}

View File

@ -0,0 +1,146 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.byte_vector;
import com.frostwire.jlibtorrent.swig.byte_vectors_pair;
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_add_scalar_public;
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_add_scalar_secret;
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_create_keypair;
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_create_seed;
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_key_exchange;
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_sign;
import static com.frostwire.jlibtorrent.swig.libtorrent.ed25519_verify;
/**
* @author gubatron
* @author aldenml
*/
public final class Ed25519 {
public final static int SEED_SIZE = 32;
public final static int PUBLIC_KEY_SIZE = 32;
public final static int SECRET_KEY_SIZE = 64;
public final static int SIGNATURE_SIZE = 64;
public final static int SCALAR_SIZE = 32;
public final static int SHARED_SECRET_SIZE = 32;
private Ed25519() {
}
/**
* @return
*/
public static byte[] createSeed() {
byte_vector seed = ed25519_create_seed();
return Vectors.byte_vector2bytes(seed);
}
/**
* @param seed
* @return
*/
public static Pair<byte[], byte[]> createKeypair(byte[] seed) {
if (seed == null || seed.length != SEED_SIZE) {
throw new IllegalArgumentException("seed must be not null and of size " + SEED_SIZE);
}
byte_vectors_pair keypair = ed25519_create_keypair(Vectors.bytes2byte_vector(seed));
return new Pair<>(Vectors.byte_vector2bytes(keypair.getFirst()),
Vectors.byte_vector2bytes(keypair.getSecond()));
}
/**
* @param message
* @param publicKey
* @param secretKey
* @return
*/
public static byte[] sign(byte[] message, byte[] publicKey, byte[] secretKey) {
if (publicKey == null || publicKey.length != PUBLIC_KEY_SIZE) {
throw new IllegalArgumentException("public key must be not null and of size " + PUBLIC_KEY_SIZE);
}
if (secretKey == null || secretKey.length != SECRET_KEY_SIZE) {
throw new IllegalArgumentException("secret key must be not null and of size " + SECRET_KEY_SIZE);
}
byte_vector signature = ed25519_sign(Vectors.bytes2byte_vector(message),
Vectors.bytes2byte_vector(publicKey),
Vectors.bytes2byte_vector(secretKey));
return Vectors.byte_vector2bytes(signature);
}
/**
* @param signature
* @param message
* @param publicKey
* @return
*/
public static boolean verify(byte[] signature, byte[] message, byte[] publicKey) {
if (signature == null || signature.length != SIGNATURE_SIZE) {
throw new IllegalArgumentException("signature must be not null and of size " + SIGNATURE_SIZE);
}
if (publicKey == null || publicKey.length != PUBLIC_KEY_SIZE) {
throw new IllegalArgumentException("public key must be not null and of size " + PUBLIC_KEY_SIZE);
}
return ed25519_verify(Vectors.bytes2byte_vector(signature),
Vectors.bytes2byte_vector(message), Vectors.bytes2byte_vector(publicKey));
}
/**
* @param publicKey
* @param scalar
* @return
*/
public static byte[] addScalarPublic(byte[] publicKey, byte[] scalar) {
if (publicKey == null || publicKey.length != PUBLIC_KEY_SIZE) {
throw new IllegalArgumentException("public key must be not null and of size " + PUBLIC_KEY_SIZE);
}
if (scalar == null || scalar.length != SCALAR_SIZE) {
throw new IllegalArgumentException("scalar must be not null and of size " + SCALAR_SIZE);
}
byte_vector ret = ed25519_add_scalar_public(Vectors.bytes2byte_vector(publicKey), Vectors.bytes2byte_vector(scalar));
return Vectors.byte_vector2bytes(ret);
}
/**
* @param secretKey
* @param scalar
* @return
*/
public static byte[] addScalarSecret(byte[] secretKey, byte[] scalar) {
if (secretKey == null || secretKey.length != SECRET_KEY_SIZE) {
throw new IllegalArgumentException("public key must be not null and of size " + SECRET_KEY_SIZE);
}
if (scalar == null || scalar.length != SCALAR_SIZE) {
throw new IllegalArgumentException("scalar must be not null and of size " + SCALAR_SIZE);
}
byte_vector ret = ed25519_add_scalar_secret(Vectors.bytes2byte_vector(secretKey), Vectors.bytes2byte_vector(scalar));
return Vectors.byte_vector2bytes(ret);
}
/**
* @param publicKey
* @param secretKey
* @return
*/
public byte[] keyExchange(byte[] publicKey, byte[] secretKey) {
if (publicKey == null || publicKey.length != PUBLIC_KEY_SIZE) {
throw new IllegalArgumentException("public key must be not null and of size " + PUBLIC_KEY_SIZE);
}
if (secretKey == null || secretKey.length != SECRET_KEY_SIZE) {
throw new IllegalArgumentException("private key must be not null and of size " + SECRET_KEY_SIZE);
}
byte_vector secret = ed25519_key_exchange(Vectors.bytes2byte_vector(publicKey),
Vectors.bytes2byte_vector(secretKey));
return Vectors.byte_vector2bytes(secret);
}
}

View File

@ -0,0 +1,226 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.entry;
import com.frostwire.jlibtorrent.swig.entry_vector;
import com.frostwire.jlibtorrent.swig.string_entry_map;
import com.frostwire.jlibtorrent.swig.string_vector;
import java.io.File;
import java.io.IOException;
import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The Entry class represents one node in a bencoded hierarchy. It works as a
* variant type, it can be either a list, a dictionary, an integer
* or a string.
*
* @author gubatron
* @author aldenml
*/
public final class Entry {
private final entry e;
public Entry(entry e) {
this.e = e;
}
public Entry(String s) {
this(new entry(s));
}
public Entry(long n) {
this(new entry(n));
}
public entry swig() {
return e;
}
public byte[] bencode() {
return Vectors.byte_vector2bytes(e.bencode());
}
public String string() {
return e.string();
}
public long integer() {
return e.integer();
}
public List<Entry> list() {
return new EntryList(e.list());
}
public Map<String, Entry> dictionary() {
return new EntryMap(e.dict());
}
@Override
public String toString() {
return e.to_string();
}
public static Entry bdecode(byte[] data) {
return new Entry(entry.bdecode(Vectors.bytes2byte_vector(data)));
}
public static Entry bdecode(File file) throws IOException {
byte[] data = Files.bytes(file);
return bdecode(data);
}
public static Entry fromList(List<?> list) {
entry e = new entry(entry.data_type.list_t);
entry_vector d = e.list();
for (Object v : list) {
if (v instanceof String) {
d.push_back(new entry((String) v));
} else if (v instanceof Integer) {
d.push_back(new entry((Integer) v));
} else if (v instanceof Entry) {
d.push_back(((Entry) v).swig());
} else if (v instanceof entry) {
d.push_back((entry) v);
} else if (v instanceof List) {
d.push_back(fromList((List<?>) v).swig());
} else if (v instanceof Map) {
d.push_back(fromMap((Map<String, ?>) v).swig());
} else {
d.push_back(new entry(v.toString()));
}
}
return new Entry(e);
}
public static Entry fromMap(Map<String, ?> map) {
entry e = new entry(entry.data_type.dictionary_t);
string_entry_map d = e.dict();
for (String k : map.keySet()) {
Object v = map.get(k);
if (v instanceof String) {
d.set(k, new entry((String) v));
} else if (v instanceof Integer) {
d.set(k, new entry((Integer) v));
} else if (v instanceof Entry) {
d.set(k, ((Entry) v).swig());
} else if (v instanceof entry) {
d.set(k, (entry) v);
} else if (v instanceof List) {
d.set(k, fromList((List<?>) v).swig());
} else if (v instanceof Map) {
d.set(k, fromMap((Map<String, ?>) v).swig());
} else {
d.set(k, new entry(v.toString()));
}
}
return new Entry(e);
}
private static final class EntryList extends AbstractList<Entry> {
private final entry_vector v;
public EntryList(entry_vector v) {
this.v = v;
}
@Override
public Entry get(int index) {
return new Entry(v.get(index));
}
@Override
public boolean add(Entry entry) {
v.push_back(entry.swig());
return true;
}
@Override
public int size() {
return (int) v.size();
}
@Override
public void clear() {
v.clear();
}
@Override
public boolean isEmpty() {
return v.empty();
}
}
private static final class EntryMap extends AbstractMap<String, Entry> {
private final string_entry_map m;
public EntryMap(string_entry_map m) {
this.m = m;
}
@Override
public com.frostwire.jlibtorrent.Entry get(Object key) {
String k = key.toString();
return m.has_key(k) ? new com.frostwire.jlibtorrent.Entry(m.get(key.toString())) : null;
}
@Override
public com.frostwire.jlibtorrent.Entry put(String key, com.frostwire.jlibtorrent.Entry value) {
com.frostwire.jlibtorrent.Entry r = get(key);
m.set(key, value.swig());
return r;
}
@Override
public int size() {
return (int) m.size();
}
@Override
public void clear() {
m.clear();
}
@Override
public boolean containsKey(Object key) {
return m.has_key(key.toString());
}
@Override
public boolean isEmpty() {
return m.empty();
}
@Override
public Set<String> keySet() {
HashSet<String> s = new HashSet<>();
string_vector v = m.keys();
int size = (int) v.size();
for (int i = 0; i < size; i++) {
s.add(v.get(i));
}
return s;
}
@Override
public Set<Entry<String, com.frostwire.jlibtorrent.Entry>> entrySet() {
throw new UnsupportedOperationException();
}
}
}

View File

@ -0,0 +1,182 @@
/*
* Created by Angel Leon (@gubatron), Alden Torres (aldenml)
*
* Licensed under the MIT License.
*/
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.ip_interface;
import com.frostwire.jlibtorrent.swig.ip_interface_vector;
import com.frostwire.jlibtorrent.swig.ip_route;
import com.frostwire.jlibtorrent.swig.ip_route_vector;
import com.frostwire.jlibtorrent.swig.libtorrent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author gubatron
* @author aldenml
*/
public final class EnumNet {
private EnumNet() {
}
public static List<IpInterface> enumInterfaces(SessionManager session) {
if (session.swig() == null) {
return Collections.emptyList();
}
ip_interface_vector v = libtorrent.enum_net_interfaces(session.swig());
int size = (int) v.size();
ArrayList<IpInterface> l = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
l.add(new IpInterface(v.get(i)));
}
return l;
}
public static List<IpRoute> enumRoutes(SessionManager session) {
if (session.swig() == null) {
return Collections.emptyList();
}
ip_route_vector v = libtorrent.enum_routes(session.swig());
int size = (int) v.size();
ArrayList<IpRoute> l = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
l.add(new IpRoute(v.get(i)));
}
return l;
}
public static Address getGateway(SessionManager session, IpInterface ipInterface, ip_route_vector routes) {
return new Address(libtorrent.get_gateway(ipInterface.swig(), routes));
}
public static final class IpInterface {
private final Address interfaceAddress;
private final Address netmask;
private final String name;
private final String friendlyName;
private final String description;
private final boolean preferred;
private final ip_interface s;
IpInterface(ip_interface iface) {
this.s = iface;
this.interfaceAddress = new Address(iface.getInterface_address());
this.netmask = new Address(iface.getNetmask());
this.name = Vectors.byte_vector2ascii(iface.getName());
this.friendlyName = Vectors.byte_vector2ascii(iface.getFriendly_name());
this.description = Vectors.byte_vector2ascii(iface.getDescription());
this.preferred = iface.getPreferred();
}
public ip_interface swig() {
return s;
}
public Address interfaceAddress() {
return interfaceAddress;
}
public Address netmask() {
return netmask;
}
public String name() {
return name;
}
public String friendlyName() {
return friendlyName;
}
public String description() {
return description;
}
public boolean preferred() {
return preferred;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("address: ").append(interfaceAddress);
sb.append(", netmask: ").append(netmask);
sb.append(", name: ").append(name);
sb.append(", friendlyName: ").append(friendlyName);
sb.append(", description: ").append(description);
sb.append(", preferred: ").append(preferred);
return sb.toString();
}
}
public static final class IpRoute {
private final Address destination;
private final Address netmask;
private final Address gateway;
private final String name;
private final int mtu;
private final ip_route s;
IpRoute(ip_route route) {
this.s = route;
this.destination = new Address(route.getDestination());
this.netmask = new Address(route.getNetmask());
this.gateway = new Address(route.getGateway());
this.name = Vectors.byte_vector2ascii(route.getName());
this.mtu = route.getMtu();
}
public ip_route swig() {
return this.s;
}
public Address destination() {
return destination;
}
public Address netmask() {
return netmask;
}
public Address gateway() {
return gateway;
}
public String name() {
return name;
}
public int mtu() {
return mtu;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("destination: ").append(destination);
sb.append(", netmask: ").append(netmask);
sb.append(", gateway: ").append(gateway);
sb.append(", name: ").append(name);
sb.append(", mtu: ").append(mtu);
return sb.toString();
}
}
}

View File

@ -0,0 +1,50 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.error_code;
/**
* @author gubatron
* @author aldenml
*/
public final class ErrorCode {
private int value;
private String message;
private boolean isError;
/**
* @param ec the native object
*/
public ErrorCode(error_code ec) {
assign(ec);
}
/**
* @return the internal error code value
*/
public int value() {
return value;
}
/**
* @return the error message
*/
public String message() {
return message;
}
/**
* Returns if this error code actually represents an error.
*
* @return true if an actual error
*/
public boolean isError() {
return isError;
}
void assign(error_code ec) {
value = ec.value();
message = ec.message();
isError = ec.op_bool();
}
}

View File

@ -0,0 +1,66 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.file_slice;
/**
* Represents a window of a file in a torrent.
* <p>
* The {@link #fileIndex()} refers to the index of the file (in the {@link com.frostwire.jlibtorrent.TorrentInfo}).
* To get the path and filename, use {@link TorrentInfo#files()}. The {@link #offset()} is the byte offset in the
* file where the range starts, and {@link #size()} is the number of bytes this range is. The {@code size + offset}
* will never be greater than the file size.
* <p>
* Implementation note: This class does not store internally a reference to the native swig
* {@link com.frostwire.jlibtorrent.swig.file_slice}. This is because we are dealing with only three integral
* values and we want to avoid keeping a reference to a memory in the native heap.
*
* @author gubatron
* @author aldenml
*/
public final class FileSlice {
private final int fileIndex;
private final long offset;
private final long size;
/**
* @param fs the native object
*/
public FileSlice(file_slice fs) {
this.fileIndex = fs.getFile_index();
this.offset = fs.getOffset();
this.size = fs.getSize();
}
/**
* The index of the file.
*
* @return the index
*/
public int fileIndex() {
return fileIndex;
}
/**
* The offset from the start of the file, in bytes.
*
* @return the offset
*/
public long offset() {
return offset;
}
/**
* The size of the window, in bytes.
*
* @return the size
*/
public long size() {
return size;
}
@Override
public String toString() {
return String.format("FileSlice(fileIndex: %d, offset: %d, size: %d)", fileIndex, offset, size);
}
}

View File

@ -0,0 +1,475 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.byte_vector;
import com.frostwire.jlibtorrent.swig.file_flags_t;
import com.frostwire.jlibtorrent.swig.file_slice_vector;
import com.frostwire.jlibtorrent.swig.file_storage;
import com.frostwire.jlibtorrent.swig.string_vector;
import com.frostwire.jlibtorrent.swig.torrent_info;
import java.io.File;
import java.util.ArrayList;
/**
* This class represents a file list and the piece
* size. Everything necessary to interpret a regular bittorrent storage
* file structure.
*
* @author gubatron
* @author aldenml
*/
public final class FileStorage {
private final file_storage fs;
private final torrent_info ti;
/**
* @param fs the native object
*/
public FileStorage(file_storage fs) {
this(fs, null);
}
/**
* Used to keep the torrent info reference around.
*
* @param fs the native object
* @param ti the torrent info to pin
*/
FileStorage(file_storage fs, torrent_info ti) {
this.fs = fs;
this.ti = ti;
}
/**
* @return the native object
*/
public file_storage swig() {
return fs;
}
/**
* This methods returns the internal torrent info or null
* if it was constructed without one.
* <p>
* This also prevent premature garbage collection in case
* the storage was created from a torrent info.
*
* @return the pinned torrent info
*/
public torrent_info ti() {
return ti;
}
/**
* Returns true if the piece length has been initialized
* on the file_storage. This is typically taken as a proxy
* of whether the file_storage as a whole is initialized or
* not.
*
* @return true if valid
*/
public boolean isValid() {
return fs.is_valid();
}
/**
* Allocates space for {@code numFiles} in the internal file list. This can
* be used to avoid reallocating the internal file list when the number
* of files to be added is known up-front.
*
* @param numFiles the number of files
*/
public void reserve(int numFiles) {
fs.reserve(numFiles);
}
/**
* Adds a file to the file storage. The {@code flags} argument sets attributes on the file.
* The file attributes is an extension and may not work in all bittorrent clients.
* <p>
* If more files than one are added, certain restrictions to their paths apply.
* In a multi-file file storage (torrent), all files must share the same root directory.
* <p>
* That is, the first path element of all files must be the same.
* This shared path element is also set to the name of the torrent. It
* can be changed by calling {@link #name(String)}.
* <p>
* The built in functions to traverse a directory to add files will
* make sure this requirement is fulfilled.
*
* @param path the path
* @param size the file size
* @param flags the file flags
* @param mtime the time
* @param symlink the symlink
*/
public void addFile(String path, long size, file_flags_t flags, int mtime, String symlink) {
fs.add_file(path, size, flags, mtime, symlink);
}
/**
* Adds a file to the file storage. The {@code flags} argument sets attributes on the file.
* The file attributes is an extension and may not work in all bittorrent clients.
* <p>
* If more files than one are added, certain restrictions to their paths apply.
* In a multi-file file storage (torrent), all files must share the same root directory.
* <p>
* That is, the first path element of all files must be the same.
* This shared path element is also set to the name of the torrent. It
* can be changed by calling {@link #name(String)}.
* <p>
* The built in functions to traverse a directory to add files will
* make sure this requirement is fulfilled.
*
* @param path the path
* @param size the file size
* @param flags the file flags
* @param mtime the time
*/
public void addFile(String path, long size, file_flags_t flags, int mtime) {
fs.add_file(path, size, flags, mtime);
}
/**
* Adds a file to the file storage. The {@code flags} argument sets attributes on the file.
* The file attributes is an extension and may not work in all bittorrent clients.
* <p>
* If more files than one are added, certain restrictions to their paths apply.
* In a multi-file file storage (torrent), all files must share the same root directory.
* <p>
* That is, the first path element of all files must be the same.
* This shared path element is also set to the name of the torrent. It
* can be changed by calling {@link #name(String)}.
* <p>
* The built in functions to traverse a directory to add files will
* make sure this requirement is fulfilled.
*
* @param path the path
* @param size the file size
* @param flags the file flags
*/
public void addFile(String path, long size, file_flags_t flags) {
fs.add_file(path, size, flags);
}
/**
* Adds a file to the file storage.
* <p>
* If more files than one are added, certain restrictions to their paths apply.
* In a multi-file file storage (torrent), all files must share the same root directory.
* <p>
* That is, the first path element of all files must be the same.
* This shared path element is also set to the name of the torrent. It
* can be changed by calling {@link #name(String)}.
* <p>
* The built in functions to traverse a directory to add files will
* make sure this requirement is fulfilled.
*
* @param p
* @param size
*/
public void addFile(String p, long size) {
fs.add_file(p, size);
}
/**
* Renames the file at {@code index} to {@code newFilename}. Keep in mind
* that filenames are expected to be UTF-8 encoded.
*
* @param index
* @param newFilename
*/
public void renameFile(int index, String newFilename) {
fs.rename_file(index, newFilename);
}
/**
* Returns a list of {@link com.frostwire.jlibtorrent.FileSlice} objects representing the portions of
* files the specified piece index, byte offset and size range overlaps.
* <p>
* This is the inverse mapping of {@link #mapFile(int, long, int)}.
*
* @param piece
* @param offset
* @param size
* @return
*/
public ArrayList<FileSlice> mapBlock(int piece, long offset, int size) {
return mapBlock(fs.map_block(piece, offset, size));
}
/**
* Returns a {@link com.frostwire.jlibtorrent.PeerRequest} representing the
* piece index, byte offset and size the specified file range overlaps.
* This is the inverse mapping of {@link #mapBlock(int, long, int)}.
* <p>
* Note that the {@link PeerRequest} return type
* is meant to hold bittorrent block requests, which may not be larger
* than 16 kiB. Mapping a range larger than that may return an overflown
* integer.
*
* @param file
* @param offset
* @param size
* @return
*/
public PeerRequest mapFile(int file, long offset, int size) {
return new PeerRequest(fs.map_file(file, offset, size));
}
/**
* Returns the number of files in the file_storage.
*
* @return
*/
public int numFiles() {
return fs.num_files();
}
/**
* Returns the total number of bytes all the files in this torrent spans.
*
* @return
*/
public long totalSize() {
return fs.total_size();
}
/**
* Returns the number of pieces in the torrent.
*
* @return the number of pieces in the torrent
*/
public int numPieces() {
return fs.num_pieces();
}
/**
* Set the number of pieces in the torrent.
*
* @param n
*/
public void numPieces(int n) {
fs.set_num_pieces(n);
}
/**
* Get the size of each piece in this torrent. This size is typically an even power
* of 2. It doesn't have to be though. It should be divisible by 16kiB however.
*
* @return
*/
public int pieceLength() {
return fs.piece_length();
}
/**
* Set the size of each piece in this torrent. This size is typically an even power
* of 2. It doesn't have to be though. It should be divisible by 16kiB however.
*
* @param l
*/
public void pieceLength(int l) {
fs.set_piece_length(l);
}
/**
* Returns the piece size of {@code index}. This will be the same as {@link #pieceLength()},
* except for the last piece, which may be shorter.
*
* @param index
* @return
*/
public int pieceSize(int index) {
return fs.piece_size(index);
}
/**
* Get the name of this torrent. For multi-file torrents, this is also
* the name of the root directory all the files are stored in.
*
* @return
*/
public String name() {
return fs.name();
}
/**
* Set the name of this torrent. For multi-file torrents, this is also
* the name of the root directory all the files are stored in.
*
* @param name
*/
public void name(String name) {
fs.set_name(name);
}
/**
* Is a sha-1 hash of the file, or 0 if none was
* provided in the torrent file. This can potentially be used to
* join a bittorrent network with other file sharing networks.
*
* @param index
* @return
*/
public Sha1Hash hash(int index) {
return new Sha1Hash(fs.hash(index));
}
/**
* returns the full path to a file.
*
* @param index
* @param savePath
* @return
*/
public String filePath(int index, String savePath) {
// not calling the corresponding swig function because internally,
// the use of the function GetStringUTFChars does not consider the case of
// a copy not made
return savePath + File.separator + fs.file_path(index);
}
/**
* Returns the full path to a file.
*
* @param index the file index
* @return the full path
*/
public String filePath(int index) {
return fs.file_path(index);
}
/**
* Returns only the name of the file, whereas
* {@link #filePath(int)} returns the path (inside the torrent file) with
* the filename appended.
*
* @param index the file index
* @return the file name
*/
public String fileName(int index) {
byte_vector v = fs.file_name(index).to_bytes();
return Vectors.byte_vector2utf8(v);
}
/**
* returns the size of a file.
*
* @param index
* @return
*/
public long fileSize(int index) {
return fs.file_size(index);
}
/**
* returns true if the file at the given
* index is a pad-file.
*
* @param index
* @return
*/
public boolean padFileAt(int index) {
return fs.pad_file_at(index);
}
/**
* returns the byte offset within the torrent file
* where this file starts. It can be used to map the file to a piece
* index (given the piece size).
*
* @param index
* @return
*/
public long fileOffset(int index) {
return fs.file_offset(index);
}
/**
* @return
*/
public ArrayList<String> paths() {
string_vector v = fs.paths();
int size = (int) v.size();
ArrayList<String> l = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
l.add(v.get(i));
}
return l;
}
/**
* This file is a pad file. The creator of the
* torrent promises the file is entirely filled with
* zeroes and does not need to be downloaded. The
* purpose is just to align the next file to either
* a block or piece boundary.
*/
public static final file_flags_t FLAG_PAD_FILE = file_storage.flag_pad_file;
/**
* This file is hidden (sets the hidden attribute
* on windows).
*/
public static final file_flags_t FLAG_HIDDEN = file_storage.flag_hidden;
/**
* This file is executable (sets the executable bit
* on posix like systems).
*/
public static final file_flags_t FLAG_EXECUTABLE = file_storage.flag_executable;
/**
* This file is a symlink. The symlink target is
* specified in a separate field
*/
public static final file_flags_t FLAG_SYMLINK = file_storage.flag_symlink;
/**
* Returns a bitmask of flags from {@link file_flags_t} that apply
* to file at {@code index}.
*
* @param index
* @return the flags
*/
public file_flags_t fileFlags(int index) {
return fs.file_flags(index);
}
/**
* Returns true if the file at the specified index has been renamed to
* have an absolute path, i.e. is not anchored in the save path of the
* torrent.
*
* @param index
* @return
*/
public boolean fileAbsolutePath(int index) {
return fs.file_absolute_path(index);
}
/**
* Returns the index of the file at the given offset in the torrent.
*
* @param offset
* @return
*/
public int fileIndexAtOffset(long offset) {
return fs.file_index_at_offset(offset);
}
static ArrayList<FileSlice> mapBlock(file_slice_vector v) {
int size = (int) v.size();
ArrayList<FileSlice> l = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
l.add(new FileSlice(v.get(i)));
}
return l;
}
}

View File

@ -0,0 +1,206 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.frostwire.jlibtorrent;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
final class Files {
private Files() {
}
private static final int EOF = -1;
/**
* Reads the contents of a file into a byte array.
* The file is always closed.
*
* @param file the file to read, must not be {@code null}
* @return the file contents, never {@code null}
* @throws IOException in case of an I/O error
* @since 1.1
*/
public static byte[] bytes(File file) throws IOException {
InputStream in = null;
try {
in = openInputStream(file);
return toByteArray(in, file.length());
} finally {
closeQuietly(in);
}
}
/**
* Opens a {@link java.io.FileInputStream} for the specified file, providing better
* error messages than simply calling <code>new FileInputStream(file)</code>.
* <p>
* At the end of the method either the stream will be successfully opened,
* or an exception will have been thrown.
* <p>
* An exception is thrown if the file does not exist.
* An exception is thrown if the file object exists but is a directory.
* An exception is thrown if the file exists but cannot be read.
*
* @param file the file to open for input, must not be {@code null}
* @return a new {@link java.io.FileInputStream} for the specified file
* @throws java.io.FileNotFoundException if the file does not exist
* @throws IOException if the file object is a directory
* @throws IOException if the file cannot be read
* @since 1.3
*/
private static FileInputStream openInputStream(File file) throws IOException {
if (file.exists()) {
if (file.isDirectory()) {
throw new IOException("File '" + file + "' exists but is a directory");
}
if (file.canRead() == false) {
throw new IOException("File '" + file + "' cannot be read");
}
} else {
throw new FileNotFoundException("File '" + file + "' does not exist");
}
return new FileInputStream(file);
}
/**
* Get contents of an <code>InputStream</code> as a <code>byte[]</code>.
* Use this method instead of <code>toByteArray(InputStream)</code>
* when <code>InputStream</code> size is known.
* <b>NOTE:</b> the method checks that the length can safely be cast to an int without truncation
* before using {@link #toByteArray(java.io.InputStream, int)} to read into the byte array.
* (Arrays can have no more than Integer.MAX_VALUE entries anyway)
*
* @param input the <code>InputStream</code> to read from
* @param size the size of <code>InputStream</code>
* @return the requested byte array
* @throws IOException if an I/O error occurs or <code>InputStream</code> size differ from parameter size
* @throws IllegalArgumentException if size is less than zero or size is greater than Integer.MAX_VALUE
* @see #toByteArray(java.io.InputStream, int)
* @since 2.1
*/
private static byte[] toByteArray(InputStream input, long size) throws IOException {
if (size > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Size cannot be greater than Integer max value: " + size);
}
return toByteArray(input, (int) size);
}
/**
* Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
* Use this method instead of <code>toByteArray(InputStream)</code>
* when <code>InputStream</code> size is known
*
* @param input the <code>InputStream</code> to read from
* @param size the size of <code>InputStream</code>
* @return the requested byte array
* @throws java.io.IOException if an I/O error occurs or <code>InputStream</code> size differ from parameter size
* @throws IllegalArgumentException if size is less than zero
* @since 2.1
*/
private static byte[] toByteArray(InputStream input, int size) throws IOException {
if (size < 0) {
throw new IllegalArgumentException("Size must be equal or greater than zero: " + size);
}
if (size == 0) {
return new byte[0];
}
byte[] data = new byte[size];
int offset = 0;
int readed;
while (offset < size && (readed = input.read(data, offset, size - offset)) != EOF) {
offset += readed;
}
if (offset != size) {
throw new IOException("Unexpected readed size. current: " + offset + ", excepted: " + size);
}
return data;
}
/**
* Unconditionally close an <code>InputStream</code>.
* <p>
* Equivalent to {@link InputStream#close()}, except any exceptions will be ignored.
* This is typically used in finally blocks.
* <p>
* Example code:
* <pre>
* byte[] data = new byte[1024];
* InputStream in = null;
* try {
* in = new FileInputStream("foo.txt");
* in.read(data);
* in.close(); //close errors are handled
* } catch (Exception e) {
* // error handling
* } finally {
* IOUtils.closeQuietly(in);
* }
* </pre>
*
* @param input the InputStream to close, may be null or already closed
*/
static void closeQuietly(InputStream input) {
closeQuietly((Closeable) input);
}
/**
* Unconditionally close a <code>Closeable</code>.
* <p>
* Equivalent to {@link java.io.Closeable#close()}, except any exceptions will be ignored.
* This is typically used in finally blocks.
* <p>
* Example code:
* <pre>
* Closeable closeable = null;
* try {
* closeable = new FileReader("foo.txt");
* // process closeable
* closeable.close();
* } catch (Exception e) {
* // error handling
* } finally {
* IOUtils.closeQuietly(closeable);
* }
* </pre>
*
* @param closeable the object to close, may be null or already closed
* @since 2.0
*/
static void closeQuietly(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (IOException ioe) {
// ignore
}
}
}

View File

@ -0,0 +1,116 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.frostwire.jlibtorrent;
/**
* Converts hexadecimal Strings.
*/
final class Hex {
private Hex() {
}
/**
* Used to build output as Hex
*/
private static final char[] DIGITS_LOWER =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The
* returned array will be half the length of the passed array, as it takes two characters to represent any given
* byte. An exception is thrown if the passed char array has an odd number of elements.
*
* @param data An array of characters containing hexadecimal digits
* @return A byte array containing binary data decoded from the supplied char array.
*/
public static byte[] decode(final char[] data) {
final int len = data.length;
final byte[] out = new byte[len >> 1];
// two characters form the hex value.
for (int i = 0, j = 0; j < len; i++) {
int f = toDigit(data[j], j) << 4;
j++;
f = f | toDigit(data[j], j);
j++;
out[i] = (byte) (f & 0xFF);
}
return out;
}
public static byte[] decode(String data) {
return decode(data.toCharArray());
}
/**
* Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
* The returned array will be double the length of the passed array, as it takes two characters to represent any
* given byte.
*
* @param data
* a byte[] to convert to Hex characters
* @return A char[] containing hexadecimal characters
*/
public static String encode(final byte[] data) {
return new String(encode(data, DIGITS_LOWER));
}
/**
* Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
* The returned array will be double the length of the passed array, as it takes two characters to represent any
* given byte.
*
* @param data
* a byte[] to convert to Hex characters
* @param toDigits
* the output alphabet
* @return A char[] containing hexadecimal characters
* @since 1.4
*/
private static char[] encode(final byte[] data, final char[] toDigits) {
final int l = data.length;
final char[] out = new char[l << 1];
// two characters form the hex value.
for (int i = 0, j = 0; i < l; i++) {
out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
out[j++] = toDigits[0x0F & data[i]];
}
return out;
}
/**
* Converts a hexadecimal character to an integer.
*
* @param ch
* A character to convert to an integer digit
* @param index
* The index of the character in the source
* @return An integer
*/
private static int toDigit(final char ch, final int index) {
final int digit = Character.digit(ch, 16);
if (digit == -1) {
throw new IllegalArgumentException("Illegal hexadecimal character " + ch + " at index " + index);
}
return digit;
}
}

View File

@ -0,0 +1,109 @@
package com.frostwire.jlibtorrent;
/**
* This is a limited capability data point series backed by
* a circular buffer logic. Used to hold equally timed sample
* of statistics.
*
* @author aldenml
* @author gubatron
*/
public final class IntSeries {
private final long[] buffer;
private int head;
private int end;
private int size;
IntSeries(long[] buffer) {
if (buffer == null) {
throw new IllegalArgumentException("buffer to hold data can't be null");
}
if (buffer.length == 0) {
throw new IllegalArgumentException("buffer to hold data can't be of size 0");
}
this.buffer = buffer;
head = -1;
end = -1;
size = 0;
}
IntSeries(int capacity) {
this(new long[capacity]);
}
void add(long value) {
if (head == -1) { // first element add
head = end = 0;
buffer[end] = value;
size = 1;
return;
}
// update buffer and pointers
end = (end + 1) % buffer.length;
buffer[end] = value;
if (end <= head) {
head = (head + 1) % buffer.length;
}
// update size
if (head <= end) {
size = 1 + (end - head);
} else {
size = buffer.length;
}
}
/**
* This method will always returns a value, but keep in mind that
* due to the nature of the circular buffer internal logic, if you pass
* past the size, you will get the sames values again.
* Use {@link #size()} for a proper boundary check.
* <p>
* Usage example:
* <pre>
* {@code
* for (int i = min(series.size(), desired_count); i >= 0; i--) {
* plotMyValueAt(i, series.get(i));
* }
* }
* </pre>
*
* @param index the value's index
* @return the value in the series
*/
public long get(int index) {
return buffer[(head + index) % size];
}
/**
* This method will always returns a value, if the series is empty
* {@code 0} is returned.
*
* @return the last value in the series
*/
public long last() {
return end != 0 ? buffer[end] : 0;
}
public int size() {
return size;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[ ");
for (int i = 0; i < buffer.length; i++) {
sb.append(buffer[i]);
if (i != buffer.length - 1) {
sb.append(", ");
}
}
sb.append(" ]");
return "{ head: " + head + ", end: " + end + ", size: " + size() + ", buffer: " + sb.toString() + " }";
}
}

View File

@ -0,0 +1,115 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.libtorrent;
import com.frostwire.jlibtorrent.swig.libtorrent_jni;
import com.frostwire.jlibtorrent.swig.stats_metric_vector;
import java.util.ArrayList;
import java.util.List;
/**
* @author gubatron
* @author aldenml
*/
public final class LibTorrent {
private LibTorrent() {
}
/**
* The version number as reported by libtorrent
*
* @return the version number
*/
public static int versionNum() {
return libtorrent.LIBTORRENT_VERSION_NUM;
}
/**
* The version string as reported by libtorrent
*
* @return the version string
*/
public static String version() {
return libtorrent.version();
}
/**
* The git revision of libtorrent the native library is using.
* <p>
* This is not the internal revision libtorrent reports, since
* that string is updated from time to time. This library can be
* using an up to date revision, this string is manually
* hardcoded in each version of jlibtorrent. See
* {@link libtorrent#LIBTORRENT_REVISION} for the libtorrent string.
*
* @return the git revision
*/
public static String revision() {
return "b5bf6c3260bd726b0181671625c007be5ad49845";
}
public static int boostVersionNum() {
return libtorrent.boost_version();
}
public static String boostVersion() {
return libtorrent.boost_lib_version();
}
public static int opensslVersionNum() {
return libtorrent.openssl_version_number();
}
public static String opensslVersion() {
return libtorrent.openssl_version_text();
}
public static String jlibtorrentVersion() {
return libtorrent_jni.jlibtorrentVersion();
}
/**
* This free function returns the list of available metrics exposed by
* libtorrent's statistics API. Each metric has a name and a *value index*.
* The value index is the index into the array in session_stats_alert where
* this metric's value can be found when the session stats is sampled (by
* calling post_session_stats()).
*
* @return the list of all metrics
*/
public static List<StatsMetric> sessionStatsMetrics() {
stats_metric_vector v = libtorrent.session_stats_metrics();
int size = (int) v.size();
ArrayList<StatsMetric> l = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
l.add(new StatsMetric(v.get(i)));
}
return l;
}
/**
* given a name of a metric, this function returns the counter index of it,
* or -1 if it could not be found. The counter index is the index into the
* values array returned by session_stats_alert.
*
* @param name the name of the metric
* @return the index of the metric
*/
public static int findMetricIdx(String name) {
return libtorrent.find_metric_idx_s(name);
}
/**
* If the native library is an ARM architecture variant, returns true
* if the running platform has NEON support.
*
* @return true if the running platform has NEON support
*/
public static boolean hasArmNeonSupport() {
return libtorrent.arm_neon_support();
}
}

View File

@ -0,0 +1,56 @@
package com.frostwire.jlibtorrent;
import java.util.logging.Level;
import static java.util.logging.Level.INFO;
/**
* @author gubatron
* @author aldenml
*/
final class Logger {
private final java.util.logging.Logger jul;
private final String name;
Logger(java.util.logging.Logger jul) {
this.jul = jul;
this.name = jul.getName();
}
public static Logger getLogger(Class<?> clazz) {
return new Logger(java.util.logging.Logger.getLogger(clazz.getName()));
}
public void info(String msg) {
jul.logp(INFO, name, "", msg);
}
public void info(String msg, Throwable e) {
jul.logp(Level.INFO, name, "", msg, e);
}
public void warn(String msg) {
jul.logp(INFO, name, "", msg);
}
public void warn(String msg, Throwable e) {
jul.logp(Level.INFO, name, "", msg, e);
}
public void error(String msg) {
jul.logp(INFO, name, "", msg);
}
public void error(String msg, Throwable e) {
jul.logp(Level.INFO, name, "", msg, e);
}
public void debug(String msg) {
jul.logp(INFO, name, "", msg);
}
public void debug(String msg, Throwable e) {
jul.logp(Level.INFO, name, "", msg, e);
}
}

View File

@ -0,0 +1,62 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.move_flags_t;
/**
* Flags for asynchronous move of a torrent handle storage.
*
* @author gubatron
* @author aldenml
* @see TorrentHandle#moveStorage(String, MoveFlags)
*/
public enum MoveFlags {
/**
* Replace any files in the destination when copying
* or moving the storage.
*/
ALWAYS_REPLACE_FILES(move_flags_t.always_replace_files),
/**
* If any files that we want to copy exist in the destination,
* fail the whole operation and don't perform
* any copy or move. There is an inherent race condition
* in this mode. The files are checked for existence before
* the operation starts. In between the check and performing
* the copy, the destination files may be created, in which
* case they are replaced.
*/
FAIL_IF_EXIST(move_flags_t.fail_if_exist),
/**
* If any files exist in the target, take those files instead
* of the ones we may have in the source.
*/
DONT_REPLACE(move_flags_t.dont_replace);
MoveFlags(move_flags_t swigValue) {
this.swigValue = swigValue;
}
private final move_flags_t swigValue;
/**
* @return the native value
*/
public move_flags_t swig() {
return swigValue;
}
/**
* @param swigValue the native value
*/
public static MoveFlags fromSwig(move_flags_t swigValue) {
MoveFlags[] enumValues = MoveFlags.class.getEnumConstants();
for (MoveFlags ev : enumValues) {
if (ev.swig() == swigValue) {
return ev;
}
}
throw new IllegalArgumentException("Enum value not supported");
}
}

View File

@ -0,0 +1,254 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.libtorrent;
import com.frostwire.jlibtorrent.swig.operation_t;
/**
* These constants are used to identify the operation that failed, causing a
* peer to disconnect.
*
* @author gubatron
* @author aldenml
*/
public enum Operation {
/**
* The error was unexpected and it is unknown which operation caused it.
*/
UNKNOWN(operation_t.unknown.swigValue()),
/**
* This is used when the bittorrent logic determines to disconnect.
*/
BITTORRENT(operation_t.bittorrent.swigValue()),
/**
* A call to iocontrol failed.
*/
IOCONTROL(operation_t.iocontrol.swigValue()),
/**
* A call to getpeername failed (querying the remote IP of a connection).
*/
GETPEERNAME(operation_t.getpeername.swigValue()),
/**
* A call to getname failed (querying the local IP of a connection).
*/
GETNAME(operation_t.getname.swigValue()),
/**
* An attempt to allocate a receive buffer failed.
*/
ALLOC_RECVBUF(operation_t.alloc_recvbuf.swigValue()),
/**
* An attempt to allocate a send buffer failed.
*/
ALLOC_SNDBUF(operation_t.alloc_sndbuf.swigValue()),
/**
* Writing to a file failed.
*/
FILE_WRITE(operation_t.file_write.swigValue()),
/**
* Reading from a file failed.
*/
FILE_READ(operation_t.file_read.swigValue()),
/**
* A non-read and non-write file operation failed.
*/
FILE(operation_t.file.swigValue()),
/**
* A socket write operation failed.
*/
SOCK_WRITE(operation_t.sock_write.swigValue()),
/**
* A socket read operation failed.
*/
SOCK_READ(operation_t.sock_read.swigValue()),
/**
* A call to open(), to create a socket socket failed.
*/
SOCK_OPEN(operation_t.sock_open.swigValue()),
/**
* A call to bind() on a socket failed.
*/
SOCK_BIND(operation_t.sock_bind.swigValue()),
/**
* An attempt to query the number of bytes available to read from a socket
* failed.
*/
AVAILABLE(operation_t.available.swigValue()),
/**
* A call related to bittorrent protocol encryption failed.
*/
ENCRYPTION(operation_t.encryption.swigValue()),
/**
* An attempt to connect a socket failed.
*/
CONNECT(operation_t.connect.swigValue()),
/**
* Establishing an SSL connection failed.
*/
SSL_HANDSHAKE(operation_t.ssl_handshake.swigValue()),
/**
* A connection failed to satisfy the bind interface setting.
*/
GET_INTERFACE(operation_t.get_interface.swigValue()),
/**
* A call to listen() on a socket.
*/
SOCK_LISTEN(operation_t.sock_listen.swigValue()),
/**
* A call to the ioctl to bind a socket to a specific network device or
* adaptor.
*/
SOCK_BIND_TO_DEVICE(operation_t.sock_bind_to_device.swigValue()),
/**
* A call to accept() on a socket.
*/
SOCK_ACCEPT(operation_t.sock_accept.swigValue()),
/**
* Convert a string into a valid network address.
*/
PARSE_ADDRESS(operation_t.parse_address.swigValue()),
/**
* Enumeration network devices or adapters.
*/
ENUM_IF(operation_t.enum_if.swigValue()),
/**
*
*/
FILE_STAT(operation_t.file_stat.swigValue()),
/**
*
*/
FILE_COPY(operation_t.file_copy.swigValue()),
/**
*
*/
FILE_FALLOCATE(operation_t.file_fallocate.swigValue()),
/**
*
*/
FILE_HARD_LINK(operation_t.file_hard_link.swigValue()),
/**
*
*/
FILE_REMOVE(operation_t.file_remove.swigValue()),
/**
*
*/
FILE_RENAME(operation_t.file_rename.swigValue()),
/**
*
*/
FILE_OPEN(operation_t.file_open.swigValue()),
/**
*
*/
MKDIR(operation_t.mkdir.swigValue()),
/**
*
*/
CHECK_RESUME(operation_t.check_resume.swigValue()),
/**
*
*/
EXCEPTION(operation_t.exception.swigValue()),
/**
*
*/
ALLOC_CACHE_PIECE(operation_t.alloc_cache_piece.swigValue()),
/**
*
*/
PARTFILE_MOVE(operation_t.partfile_move.swigValue()),
/**
*
*/
PARTFILE_READ(operation_t.partfile_read.swigValue()),
/**
*
*/
PARTFILE_WRITE(operation_t.partfile_write.swigValue()),
/**
*
*/
HOSTNAME_LOOKUP(operation_t.hostname_lookup.swigValue());
Operation(int swigValue) {
this.swigValue = swigValue;
}
private final int swigValue;
/**
* @return the native value.
*/
public int swig() {
return swigValue;
}
public String nativeName() {
try {
return libtorrent.operation_name(operation_t.swigToEnum(swigValue));
} catch (Throwable e) {
return "invalid enum value";
}
}
/**
* @param swigValue the native value
* @return the swig enum.
*/
public static Operation fromSwig(int swigValue) {
Operation[] enumValues = Operation.class.getEnumConstants();
for (Operation ev : enumValues) {
if (ev.swig() == swigValue) {
return ev;
}
}
return UNKNOWN;
}
/**
* @param swigValue the native enum
* @return the swig enum.
*/
public static Operation fromSwig(operation_t swigValue) {
return fromSwig(swigValue.swigValue());
}
}

View File

@ -0,0 +1,54 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.string_int_pair;
import com.frostwire.jlibtorrent.swig.string_string_pair;
/**
* Utility function to mirror the C++ std::pair class
*
* @author gubatron
* @author aldenml
*/
public final class Pair<T1, T2> {
/**
* @param first first element
* @param second second element
*/
public Pair(T1 first, T2 second) {
this.first = first;
this.second = second;
}
/**
* the first element
*/
public final T1 first;
/**
* the second element
*/
public final T2 second;
/**
* @return a native object
*/
string_string_pair to_string_string_pair() {
if (!String.class.equals(first.getClass()) || !String.class.equals(second.getClass())) {
throw new IllegalArgumentException("Incompatible types");
}
return new string_string_pair((String) first, (String) second);
}
/**
* @return a native object
*/
string_int_pair to_string_int_pair() {
if (!String.class.equals(first.getClass()) || !Integer.class.equals(second.getClass())) {
throw new IllegalArgumentException("Incompatible types");
}
return new string_int_pair((String) first, (Integer) second);
}
}

View File

@ -0,0 +1,77 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.partial_piece_info;
/**
* This class holds information about pieces that have outstanding
* requests or outstanding writes.
*
* @author gubatron
* @author aldenml
*/
public final class PartialPieceInfo {
private final partial_piece_info p;
/**
* @param p the native object
*/
public PartialPieceInfo(partial_piece_info p) {
this.p = p;
}
/**
* @return the native object
*/
public partial_piece_info swig() {
return p;
}
/**
* The index of the piece in question. {@link #blocksInPiece()} is
* the number of blocks in this particular piece. This number will
* be the same for most pieces, but the last piece may have fewer
* blocks than the standard pieces.
*
* @return the piece index
*/
public int pieceIndex() {
return p.getPiece_index();
}
/**
* The number of blocks in this piece.
*
* @return the number of blocks
*/
public int blocksInPiece() {
return p.getBlocks_in_piece();
}
/**
* The number of blocks that are in the finished state.
*
* @return the number of finished blocks
*/
public int finished() {
return p.getFinished();
}
/**
* The number of blocks that are in the writing state.
*
* @return the number of blocks in writing state
*/
public int writing() {
return p.getWriting();
}
/**
* The number of blocks that are in the requested state.
*
* @return the number of blocks in requested state
*/
public int requested() {
return p.getRequested();
}
}

View File

@ -0,0 +1,218 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.peer_info;
/**
* Holds information and statistics about one peer
* that libtorrent is connected to.
* <p>
* This class is a lightweight version of the native {@link peer_info}, and
* only carries a subset of all the information. However, it's completely open
* for custom use or optimization to accommodate client necessities.
*
* @author gubatron
* @author aldenml
*/
public class PeerInfo {
protected String client;
protected long totalDownload;
protected long totalUpload;
protected int flags;
protected byte source;
protected int upSpeed;
protected int downSpeed;
protected ConnectionType connectionType;
protected float progress;
protected int progressPpm;
protected String ip;
public PeerInfo(peer_info p) {
init(p);
}
/**
* This describe the software at the other end of the connection.
* In some cases this information is not available, then it will contain
* a string that may give away something about which software is running
* in the other end. In the case of a web seed, the server type and
* version will be a part of this string.
*
* @return the client string
*/
public String client() {
return client;
}
/**
* The total number of bytes downloaded from this peer.
* These numbers do not include the protocol chatter, but only the
* payload data.
*
* @return number of bytes downloaded
*/
public long totalDownload() {
return totalDownload;
}
/**
* The total number of bytes uploaded to this peer.
* These numbers do not include the protocol chatter, but only the
* payload data.
*
* @return number of bytes uploaded
*/
public long totalUpload() {
return totalUpload;
}
/**
* Tells you in which state the peer is in. It is set to
* any combination of the peer_flags_t flags.
*
* @return the flags as an integer
*/
public int flags() {
return flags;
}
/**
* A combination of flags describing from which sources this peer
* was received. A combination of the peer_source_flags_t flags.
*
* @return the flags as a byte
*/
public byte source() {
return source;
}
/**
* The current upload speed we have to and from this peer
* (including any protocol messages). Updated about once per second
*
* @return current upload speed we have to and from this peer
*/
public int upSpeed() {
return upSpeed;
}
/**
* The current download speed we have to and from this peer
* (including any protocol messages). Updated about once per second
*
* @return current download speed we have to and from this peer
*/
public int downSpeed() {
return downSpeed;
}
/**
* The kind of connection this peer uses.
*
* @return the connection type
*/
public ConnectionType connectionType() {
return connectionType;
}
/**
* The progress of the peer in the range [0, 1]. This is always 0 when
* floating point operations are disabled, instead use ``progress_ppm``.
*
* @return the progress of the peer in the range [0, 1]
*/
public float progress() {
return progress;
}
/**
* Indicates the download progress of the peer in the range [0, 1000000]
* (parts per million).
*
* @return the download progress of the peer in the range [0, 1000000]
*/
public int progressPpm() {
return progressPpm;
}
/**
* The IP-address to this peer.
*
* @return a string representing the endpoint.
*/
public String ip() {
return ip;
}
/**
* NOTE: use this with care and only if necessary.
*
* @param p the native object
*/
protected void init(peer_info p) {
client = Vectors.byte_vector2utf8(p.get_client());
totalDownload = p.getTotal_download();
totalUpload = p.getTotal_upload();
flags = p.get_flags();
source = p.get_source();
upSpeed = p.getUp_speed();
downSpeed = p.getDown_speed();
connectionType = ConnectionType.fromSwig(p.getConnection_type());
progress = p.getProgress();
progressPpm = p.getProgress_ppm();
ip = new TcpEndpoint(p.getIp()).toString();
}
/**
* The kind of connection this is. Used for the connectionType field.
*/
public enum ConnectionType {
/**
* Regular bittorrent connection.
*/
STANDARD_BITTORRENT(peer_info.connection_type_t.standard_bittorrent.swigValue()),
/**
* HTTP connection using the `BEP 19`_ protocol
*/
WEB_SEED(peer_info.connection_type_t.web_seed.swigValue()),
/**
* HTTP connection using the `BEP 17`_ protocol.
*/
HTTP_SEED(peer_info.connection_type_t.http_seed.swigValue()),
/**
*
*/
UNKNOWN(-1);
ConnectionType(int swigValue) {
this.swigValue = swigValue;
}
private final int swigValue;
/**
* @return the native value
*/
public int swig() {
return swigValue;
}
/**
* @param swigValue the swig value
* @return the enum value
*/
public static ConnectionType fromSwig(int swigValue) {
ConnectionType[] enumValues = ConnectionType.class.getEnumConstants();
for (ConnectionType ev : enumValues) {
if (ev.swig() == swigValue) {
return ev;
}
}
return UNKNOWN;
}
}
}

View File

@ -0,0 +1,62 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.peer_request;
/**
* Represents a byte range within a piece. Internally this is
* is used for incoming piece requests.
*
* @author gubatron
* @author aldenml
*/
public final class PeerRequest {
private final peer_request r;
// internal
public PeerRequest(peer_request r) {
this.r = r;
}
/**
* @return native object
*/
public peer_request swig() {
return r;
}
/**
* The index of the piece in which the range starts.
*
* @return the piece index
*/
public int piece() {
return r.getPiece();
}
/**
* The offset within that piece where the range starts.
*
* @return the start offset
*/
public int start() {
return r.getStart();
}
/**
* The size of the range, in bytes.
*
* @return the range length
*/
public int length() {
return r.getLength();
}
/**
* @return string representation
*/
@Override
public String toString() {
return "PeerRequest(piece: " + piece() + ", start: " + start() + ", length: " + length() + ")";
}
}

View File

@ -0,0 +1,177 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.piece_index_bitfield;
import com.frostwire.jlibtorrent.swig.torrent_status;
/**
* The bitfield type stores any number of bits as a bitfield
* in a heap allocated array.
*
* @author gubatron
* @author aldenml
*/
public final class PieceIndexBitfield {
private final piece_index_bitfield f;
private final torrent_status ts;
/**
* @param f the native object
*/
public PieceIndexBitfield(piece_index_bitfield f) {
this(f, null);
}
/**
* Used to keep the torrent status reference around.
*
* @param f the native object
* @param ts the torrent status to pin
*/
PieceIndexBitfield(piece_index_bitfield f, torrent_status ts) {
this.f = f;
this.ts = ts;
}
/**
* @return the native object
*/
public piece_index_bitfield swig() {
return f;
}
/**
* This methods returns the internal torrent status or null
* if it was constructed without one.
* <p>
* This also prevent premature garbage collection in case
* the storage was created from a torrent status.
*
* @return the pinned torrent info
*/
public torrent_status ts() {
return ts;
}
/**
* @param index the bit index
* @return the bit value
*/
public boolean getBit(int index) {
return f.get_bit(index);
}
/**
* @param index the bit index
*/
public void clearBit(int index) {
f.clear_bit(index);
}
/**
* @param index the bit index
*/
public void setBit(int index) {
f.set_bit(index);
}
public int endIndex() {
return f.end_index();
}
/**
* Returns true if all bits in the bitfield are set.
*
* @return true if all bits are set
*/
public boolean isAllSet() {
return f.all_set();
}
/**
* @return true if no bit is set
*/
public boolean isNoneSet() {
return f.none_set();
}
/**
* Returns the size of the bitfield in bits.
*
* @return the size
*/
public int size() {
return f.size();
}
/**
* Returns true if the bitfield has zero size.
*
* @return true if empty
*/
public boolean isEmpty() {
return f.empty();
}
/**
* Counts the number of bits in the bitfield that are set to 1.
*
* @return the number of bits set
*/
public int count() {
return f.count();
}
/**
* @return the bit index
*/
public int findFirstSet() {
return f.find_first_set();
}
/**
* @return the bit index
*/
public int findLastClear() {
return f.find_last_clear();
}
/**
* Set the size of the bitfield to ``bits`` length. If the bitfield is extended,
* the new bits are initialized to ``val``.
*
* @param bits the number of bits
* @param val the bits value
*/
public void resize(int bits, boolean val) {
f.resize(bits, val);
}
/**
* @param bits the number of bits
*/
public void resize(int bits) {
f.resize(bits);
}
/**
* Set all bits in the bitfield to 1 (set_all) or 0 (clear_all).
*/
public void setAll() {
f.set_all();
}
/**
* Set all bits in the bitfield to 1 (set_all) or 0 (clear_all).
*/
public void clearAll() {
f.clear_all();
}
/**
* Make the bitfield empty, of zero size.
*/
public void clear() {
f.clear();
}
}

View File

@ -0,0 +1,103 @@
package com.frostwire.jlibtorrent;
import java.util.ArrayList;
/**
* @author gubatron
* @author aldenml
*/
public final class PiecesTracker {
private final int numFiles;
private final int numPieces;
private final int[][] files;
private final long[][] sizes;
private final boolean[] complete;
public PiecesTracker(TorrentInfo ti) {
this.numFiles = ti.numFiles();
this.numPieces = ti.numPieces();
this.files = new int[numFiles][];
this.sizes = new long[numFiles][];
this.complete = new boolean[numPieces];
FileStorage fs = ti.files();
for (int fileIndex = 0; fileIndex < numFiles; fileIndex++) {
long size = fs.fileSize(fileIndex);
int firstPiece = ti.mapFile(fileIndex, 0, 1).piece();
int lastPiece = ti.mapFile(fileIndex, size - 1, 1).piece();
int numSlices = lastPiece - firstPiece + 1;
files[fileIndex] = new int[numSlices];
sizes[fileIndex] = new long[numSlices];
for (int pieceIndex = firstPiece; pieceIndex <= lastPiece; pieceIndex++) {
int index = pieceIndex - firstPiece;
files[fileIndex][index] = pieceIndex;
ArrayList<FileSlice> slices = ti.mapBlock(pieceIndex, 0, ti.pieceSize(pieceIndex));
for (FileSlice slice : slices) {
if (slice.fileIndex() == fileIndex) {
sizes[fileIndex][index] = slice.size();
}
}
}
}
}
public int numFiles() {
return numFiles;
}
public int numPieces() {
return numPieces;
}
public boolean isComplete(int pieceIndex) {
return complete[pieceIndex];
}
public void setComplete(int pieceIndex, boolean complete) {
this.complete[pieceIndex] = complete;
}
public long getSequentialDownloadedBytes(int fileIndex) {
int[] pieces = files[fileIndex];
long downloaded = 0;
for (int i = 0; i < pieces.length; i++) {
int pieceIndex = pieces[i];
if (complete[pieceIndex]) {
downloaded += sizes[fileIndex][i];
} else {
break;
}
}
return downloaded;
}
public int getSequentialDownloadedPieces(int fileIndex) {
int[] pieces = files[fileIndex];
int count = 0;
for (int i = 0; i < pieces.length; i++) {
int pieceIndex = pieces[i];
if (complete[pieceIndex]) {
count++;
} else {
break;
}
}
return count;
}
}

View File

@ -0,0 +1,10 @@
package com.frostwire.jlibtorrent;
/**
* @author gubatron
* @author aldenml
*/
public interface Plugin {
boolean onDhtRequest(String query, UdpEndpoint source, BDecodeNode message, Entry response);
}

View File

@ -0,0 +1,36 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.portmap_protocol;
/**
* @author gubatron
* @author aldenml
*/
public enum PortmapProtocol {
NONE(portmap_protocol.none.swigValue()),
TCP(portmap_protocol.tcp.swigValue()),
UDP(portmap_protocol.udp.swigValue());
PortmapProtocol(int swigValue) {
this.swigValue = swigValue;
}
private final int swigValue;
public int swig() {
return swigValue;
}
public static PortmapProtocol fromSwig(int swigValue) {
PortmapProtocol[] enumValues = PortmapProtocol.class.getEnumConstants();
for (PortmapProtocol ev : enumValues) {
if (ev.swig() == swigValue) {
return ev;
}
}
throw new IllegalArgumentException("No enum " + PortmapProtocol.class + " with value " + swigValue);
}
}

View File

@ -0,0 +1,34 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.portmap_transport;
/**
* @author gubatron
* @author aldenml
*/
public enum PortmapTransport {
NAT_PMP(portmap_transport.natpmp.swigValue()),
UPNP(portmap_transport.upnp.swigValue());
PortmapTransport(int swigValue) {
this.swigValue = swigValue;
}
private final int swigValue;
public int swig() {
return swigValue;
}
public static PortmapTransport fromSwig(int swigValue) {
PortmapTransport[] enumValues = PortmapTransport.class.getEnumConstants();
for (PortmapTransport ev : enumValues) {
if (ev.swig() == swigValue) {
return ev;
}
}
throw new IllegalArgumentException("No enum " + PortmapTransport.class + " with value " + swigValue);
}
}

View File

@ -0,0 +1,113 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.byte_vector;
import com.frostwire.jlibtorrent.swig.int_vector;
/**
* @author gubatron
* @author aldenml
*/
public enum Priority {
/**
* piece or file is not downloaded at all
*/
IGNORE(0),
/**
* normal priority. Download order is dependent on availability
*/
NORMAL(1),
/**
* higher than normal priority. Pieces are preferred over pieces with
* the same availability, but not over pieces with lower availability
*/
TWO(2),
/**
* pieces are as likely to be picked as partial pieces.
*/
THREE(3),
/**
* pieces are preferred over partial pieces, but not over pieces with
* lower availability
*/
FOUR(4),
/**
* *currently the same as 4*
*/
FIVE(5),
/**
* piece is as likely to be picked as any piece with availability 1
*/
SIX(6),
/**
* maximum priority, availability is disregarded, the piece is
* preferred over any other piece with lower priority
*/
SEVEN(7);
Priority(int swigValue) {
this.swigValue = swigValue;
}
private final int swigValue;
/**
* @return the native value
*/
public int swig() {
return swigValue;
}
/**
* @param swigValue the native value
* @return the enum corresponding value
*/
public static Priority fromSwig(int swigValue) {
Priority[] enumValues = Priority.class.getEnumConstants();
for (Priority ev : enumValues) {
if (ev.swig() == swigValue) {
return ev;
}
}
throw new IllegalArgumentException("Invalid native value");
}
public static Priority[] array(Priority value, int size) {
Priority[] arr = new Priority[size];
for (int i = 0; i < size; i++) {
arr[i] = value;
}
return arr;
}
static int_vector array2vector(Priority[] arr) {
int_vector v = new int_vector();
for (int i = 0; i < arr.length; i++) {
Priority p = arr[i];
v.push_back(p.swig());
}
return v;
}
static byte_vector array2byte_vector(Priority[] arr) {
byte_vector v = new byte_vector();
for (int i = 0; i < arr.length; i++) {
Priority p = arr[i];
v.push_back((byte) p.swig());
}
return v;
}
}

View File

@ -0,0 +1,642 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.alerts.DhtImmutableItemAlert;
import com.frostwire.jlibtorrent.alerts.DhtMutableItemAlert;
import com.frostwire.jlibtorrent.swig.bdecode_node;
import com.frostwire.jlibtorrent.swig.byte_vector;
import com.frostwire.jlibtorrent.swig.entry;
import com.frostwire.jlibtorrent.swig.error_code;
import com.frostwire.jlibtorrent.swig.libtorrent_errors;
import com.frostwire.jlibtorrent.swig.port_mapping_t_vector;
import com.frostwire.jlibtorrent.swig.portmap_protocol;
import com.frostwire.jlibtorrent.swig.remove_flags_t;
import com.frostwire.jlibtorrent.swig.reopen_network_flags_t;
import com.frostwire.jlibtorrent.swig.save_state_flags_t;
import com.frostwire.jlibtorrent.swig.session_flags_t;
import com.frostwire.jlibtorrent.swig.session_handle;
import com.frostwire.jlibtorrent.swig.status_flags_t;
import com.frostwire.jlibtorrent.swig.torrent_handle;
import com.frostwire.jlibtorrent.swig.torrent_handle_vector;
import java.util.ArrayList;
import java.util.List;
/**
* The session holds all state that spans multiple torrents. Among other
* things it runs the network loop and manages all torrents. Once it's
* created, the session object will spawn the main thread that will do all
* the work. The main thread will be idle as long it doesn't have any
* torrents to participate in.
* <p>
* This class belongs to a middle logical layer of abstraction. It's a wrapper
* of the underlying swig session object (from libtorrent), but it does not
* expose all the raw features.
*
* @author gubatron
* @author aldenml
*/
public class SessionHandle {
private static final Logger LOG = Logger.getLogger(SessionHandle.class);
protected final session_handle s;
/**
* @param s the native object
*/
public SessionHandle(session_handle s) {
this.s = s;
}
/**
* @return the native object
*/
public session_handle swig() {
return s;
}
public boolean isValid() {
return s.is_valid();
}
/**
* When set, the session will start paused. Call SessionHandle::resume() to start
*/
public static final session_flags_t PAUSED = session_handle.paused;
/**
* Saves settings (i.e. the {@link SettingsPack}).
*/
public static final save_state_flags_t SAVE_SETTINGS = session_handle.save_settings;
/**
* Saves {@link DhtSettings}.
*/
public static final save_state_flags_t SAVE_DHT_SETTINGS = session_handle.save_dht_settings;
/**
* Saves dht state such as nodes and node-id, possibly accelerating
* joining the DHT if provided at next session startup.
*/
public static final save_state_flags_t SAVE_DHT_STATE = session_handle.save_dht_state;
/**
* Loads and saves all session settings, including dht settings,
* encryption settings and proxy settings. This method
* internally writes all keys to an {@link entry} that is returned
* as a bencoded byte array.
* <p>
* The {@code flags} argument passed in to this method can be used to
* filter which parts of the session state to save. By default, all state
* is saved (except for the individual torrents).
*
* @return the bencoded byte array
*/
public byte[] saveState(save_state_flags_t flags) {
entry e = new entry();
s.save_state(e, flags);
return Vectors.byte_vector2bytes(e.bencode());
}
/**
* Same as calling {@link #saveState(save_state_flags_t)} with all save state flags.
*
* @return the bencoded byte array
*/
public byte[] saveState() {
entry e = new entry();
s.save_state(e);
return Vectors.byte_vector2bytes(e.bencode());
}
/**
* Loads all session settings, including DHT settings,
* encryption settings and proxy settings.
* <p>
* This method expects a byte array that it is a
* bencoded buffer.
* <p>
* The {@code flags} argument passed in to this method can be used to
* filter which parts of the session state to load. By default, all state
* is restored (except for the individual torrents).
*
* @param data the bencoded byte array
*/
public void loadState(byte[] data, save_state_flags_t flags) {
byte_vector buffer = Vectors.bytes2byte_vector(data);
bdecode_node n = new bdecode_node();
error_code ec = new error_code();
int ret = bdecode_node.bdecode(buffer, n, ec);
if (ret == 0) {
s.load_state(n, flags);
buffer.clear(); // prevents GC
} else {
LOG.error("failed to decode bencoded data: " + ec.message());
}
}
/**
* Same as calling {@link #loadState(byte[], save_state_flags_t)} with all
* save state flags.
*/
public void loadState(byte[] data) {
byte_vector buffer = Vectors.bytes2byte_vector(data);
bdecode_node n = new bdecode_node();
error_code ec = new error_code();
int ret = bdecode_node.bdecode(buffer, n, ec);
if (ret == 0) {
s.load_state(n);
buffer.clear(); // prevents GC
} else {
LOG.error("failed to decode bencoded data: " + ec.message());
}
}
/**
* This functions instructs the session to post the
* {@link com.frostwire.jlibtorrent.alerts.StateUpdateAlert},
* containing the status of all torrents whose state changed since the
* last time this function was called.
* <p>
* Only torrents who has the state subscription flag set will be
* included. This flag is on by default. See {@link AddTorrentParams}.
* the {@code flags} argument is the same as for torrent_handle::status().
*
* @param flags or-combination of native values
*/
public void postTorrentUpdates(status_flags_t flags) {
s.post_torrent_updates(flags);
}
/**
* This functions instructs the session to post the
* {@link com.frostwire.jlibtorrent.alerts.StateUpdateAlert},
* containing the status of all torrents whose state changed since the
* last time this function was called.
* <p>
* Only torrents who has the state subscription flag set will be
* included.
*/
public void postTorrentUpdates() {
s.post_torrent_updates();
}
/**
* This function will post a {@link com.frostwire.jlibtorrent.alerts.SessionStatsAlert} object, containing a
* snapshot of the performance counters from the internals of libtorrent.
* To interpret these counters, query the session via
* session_stats_metrics().
*/
public void postSessionStats() {
s.post_session_stats();
}
/**
* This will cause a dht_stats_alert to be posted.
*/
public void postDhtStats() {
s.post_dht_stats();
}
/**
* Looks for a torrent with the given info-hash. In case there is such
* a torrent in the session, a {@link TorrentHandle} to that torrent
* is returned.
* <p>
* In case the torrent cannot be found, a null is returned.
*
* @param infoHash
* @return
*/
public TorrentHandle findTorrent(Sha1Hash infoHash) {
torrent_handle th = s.find_torrent(infoHash.swig());
return th != null && th.is_valid() ? new TorrentHandle(th) : null;
}
/**
* Returns a list of torrent handles to all the
* torrents currently in the session.
*
* @return
*/
public List<TorrentHandle> torrents() {
torrent_handle_vector v = s.get_torrents();
int size = (int) v.size();
ArrayList<TorrentHandle> l = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
l.add(new TorrentHandle(v.get(i)));
}
return l;
}
/**
* You add torrents through the {@link #addTorrent(AddTorrentParams, ErrorCode)}
* function where you give an object with all the parameters.
* The {@code addTorrent} overloads will block
* until the torrent has been added (or failed to be added) and returns
* an error code and a {@link TorrentHandle}. In order to add torrents more
* efficiently, consider using {@link #asyncAddTorrent(AddTorrentParams)}
* which returns immediately, without waiting for the torrent to add.
* Notification of the torrent being added is sent as
* {@link com.frostwire.jlibtorrent.alerts.AddTorrentAlert}.
* <p>
* The {@link TorrentHandle} returned by this method can be used to retrieve
* information about the torrent's progress, its peers etc. It is also
* used to abort a torrent.
* <p>
* If the torrent you are trying to add already exists in the session (is
* either queued for checking, being checked or downloading)
* the error code will be set to {@link libtorrent_errors#duplicate_torrent}
* unless {@code flag_duplicate_is_error}
* is set to false. In that case, {@code addTorrent} will return the handle
* to the existing torrent.
* <p>
* All torrent handles must be destructed before the session is destructed!
*
* @param params the parameters to create the torrent download
* @param ec the error code if no torrent handle was created
* @return the torrent handle, could be invalid
*/
public TorrentHandle addTorrent(AddTorrentParams params, ErrorCode ec) {
error_code e = new error_code();
TorrentHandle th = new TorrentHandle(s.add_torrent(params.swig(), e));
ec.assign(e);
return th;
}
public void asyncAddTorrent(AddTorrentParams params) {
s.async_add_torrent(params.swig());
}
/**
* Delete the files belonging to the torrent from disk,
* including the part-file, if there is one.
*/
public static final remove_flags_t DELETE_FILES = session_handle.delete_files;
/**
* Delete just the part-file associated with this torrent.
*/
public static final remove_flags_t DELETE_PARTFILE = session_handle.delete_partfile;
/**
* This method will close all peer connections associated with the torrent and tell the
* tracker that we've stopped participating in the swarm. This operation cannot fail.
* When it completes, you will receive a torrent_removed_alert.
* <p>
* The optional second argument options can be used to delete all the files downloaded
* by this torrent. To do so, pass in the value session::delete_files. The removal of
* the torrent is asynchronous, there is no guarantee that adding the same torrent immediately
* after it was removed will not throw a libtorrent_exception exception. Once the torrent
* is deleted, a torrent_deleted_alert is posted.
*
* @param th the handle
*/
public void removeTorrent(TorrentHandle th, remove_flags_t options) {
if (th.isValid()) {
s.remove_torrent(th.swig(), options);
}
}
/**
* This option indicates if the ports are mapped using natpmp
* and UPnP. If mapping was already made, they are deleted and added
* again. This only works if natpmp and/or upnp are configured to be
* enable.
*/
public static final reopen_network_flags_t REOPEN_MAP_PORTS = session_handle.reopen_map_ports;
public static final int DHT_ANNOUNCE_SEED = 1;
public static final int DHT_ANNOUNCE_IMPLIED_PORT = 1 << 1;
public static final int DHT_ANNOUNCE_SSL_TORRENT = 1 << 2;
// starts/stops UPnP, NATPMP or LSD port mappers they are stopped by
// default These functions are not available in case
// ``TORRENT_DISABLE_DHT`` is defined. ``start_dht`` starts the dht node
// and makes the trackerless service available to torrents. The startup
// state is optional and can contain nodes and the node id from the
// previous session. The dht node state is a bencoded dictionary with the
// following entries:
//
// nodes
// A list of strings, where each string is a node endpoint encoded in
// binary. If the string is 6 bytes long, it is an IPv4 address of 4
// bytes, encoded in network byte order (big endian), followed by a 2
// byte port number (also network byte order). If the string is 18
// bytes long, it is 16 bytes of IPv6 address followed by a 2 bytes
// port number (also network byte order).
//
// node-id
// The node id written as a readable string as a hexadecimal number.
//
// ``dht_state`` will return the current state of the dht node, this can
// be used to start up the node again, passing this entry to
// ``start_dht``. It is a good idea to save this to disk when the session
// is closed, and read it up again when starting.
//
// If the port the DHT is supposed to listen on is already in use, and
// exception is thrown, ``asio::error``.
//
// ``stop_dht`` stops the dht node.
//
// ``add_dht_node`` adds a node to the routing table. This can be used if
// your client has its own source of bootstrapping nodes.
//
// ``set_dht_settings`` sets some parameters available to the dht node.
// See dht_settings for more information.
//
// ``is_dht_running()`` returns true if the DHT support has been started
// and false
// otherwise.
void setDhtSettings(DhtSettings settings) {
s.set_dht_settings(settings.swig());
}
public boolean isDhtRunning() {
return s.is_dht_running();
}
/**
* takes a host name and port pair. That endpoint will be
* pinged, and if a valid DHT reply is received, the node will be added to
* the routing table.
*
* @param node
*/
public void addDhtNode(Pair<String, Integer> node) {
s.add_dht_node(node.to_string_int_pair());
}
/**
* Applies the settings specified by the settings pack {@code sp}. This is an
* asynchronous operation that will return immediately and actually apply
* the settings to the main thread of libtorrent some time later.
*
* @param sp the settings
*/
public void applySettings(SettingsPack sp) {
s.apply_settings(sp.swig());
}
/**
* @return a copy of the internal settings
*/
public SettingsPack settings() {
return new SettingsPack(s.get_settings());
}
/**
* Adds a port forwarding on UPnP and/or NAT-PMP, using PCP if supported,
* whichever is enabled. The return value is a handle referring to the
* port mapping that was just created. Pass it to {@link #deletePortMapping}
* to remove it.
*
* @param t the mapping protocol
* @param externalPort the external port
* @param localPort the local port
* @return the array of port mapping ids
*/
public int[] addPortMapping(PortmapProtocol t, int externalPort, int localPort) {
port_mapping_t_vector v = s.add_port_mapping(portmap_protocol.swigToEnum(t.swig()), externalPort, localPort);
int size = (int) v.size();
int[] arr = new int[size];
for (int i = 0; i < size; i++) {
arr[i] = v.get(i);
}
return arr;
}
public void deletePortMapping(int handle) {
s.delete_port_mapping(handle);
}
/**
* This method will close all peer connections associated with the torrent and tell the
* tracker that we've stopped participating in the swarm. This operation cannot fail.
* When it completes, you will receive a torrent_removed_alert.
*
* @param th
*/
public void removeTorrent(TorrentHandle th) {
if (th.isValid()) {
s.remove_torrent(th.swig());
}
}
/**
* Instructs the session to reopen all listen and outgoing sockets.
* <p>
* It's useful in the case your platform doesn't support the built in
* IP notifier mechanism, or if you have a better more reliable way to
* detect changes in the IP routing table.
*
* @param options the options
*/
public void reopenNetworkSockets(reopen_network_flags_t options) {
s.reopen_network_sockets(options);
}
/**
* Instructs the session to reopen all listen and outgoing sockets.
* <p>
* It's useful in the case your platform doesn't support the built in
* IP notifier mechanism, or if you have a better more reliable way to
* detect changes in the IP routing table.
*/
public void reopenNetworkSockets() {
s.reopen_network_sockets();
}
/**
* Query the DHT for an immutable item at the target hash.
* the result is posted as a {@link DhtImmutableItemAlert}.
*
* @param target
*/
public void dhtGetItem(Sha1Hash target) {
s.dht_get_item(target.swig());
}
/**
* Query the DHT for a mutable item under the public key {@code key}.
* this is an ed25519 key. The {@code salt} argument is optional and may be left
* as an empty string if no salt is to be used.
* <p>
* if the item is found in the DHT, a {@link DhtMutableItemAlert} is
* posted.
*
* @param key
* @param salt
*/
public void dhtGetItem(byte[] key, byte[] salt) {
s.dht_get_item(Vectors.bytes2byte_vector(key), Vectors.bytes2byte_vector(salt));
}
/**
* Store the given bencoded data as an immutable item in the DHT.
* the returned hash is the key that is to be used to look the item
* up again. It's just the sha-1 hash of the bencoded form of the
* structure.
*
* @param entry
* @return
*/
public Sha1Hash dhtPutItem(Entry entry) {
return new Sha1Hash(s.dht_put_item(entry.swig()));
}
// store an immutable item. The ``key`` is the public key the blob is
// to be stored under. The optional ``salt`` argument is a string that
// is to be mixed in with the key when determining where in the DHT
// the value is to be stored. The callback function is called from within
// the libtorrent network thread once we've found where to store the blob,
// possibly with the current value stored under the key.
// The values passed to the callback functions are:
//
// entry& value
// the current value stored under the key (may be empty). Also expected
// to be set to the value to be stored by the function.
//
// boost::array<char,64>& signature
// the signature authenticating the current value. This may be zeroes
// if there is currently no value stored. The function is expected to
// fill in this buffer with the signature of the new value to store.
// To generate the signature, you may want to use the
// ``sign_mutable_item`` function.
//
// boost::uint64_t& seq
// current sequence number. May be zero if there is no current value.
// The function is expected to set this to the new sequence number of
// the value that is to be stored. Sequence numbers must be monotonically
// increasing. Attempting to overwrite a value with a lower or equal
// sequence number will fail, even if the signature is correct.
//
// std::string const& salt
// this is the salt that was used for this put call.
//
// Since the callback function ``cb`` is called from within libtorrent,
// it is critical to not perform any blocking operations. Ideally not
// even locking a mutex. Pass any data required for this function along
// with the function object's context and make the function entirely
// self-contained. The only reason data blobs' values are computed
// via a function instead of just passing in the new value is to avoid
// race conditions. If you want to *update* the value in the DHT, you
// must first retrieve it, then modify it, then write it back. The way
// the DHT works, it is natural to always do a lookup before storing and
// calling the callback in between is convenient.
public void dhtPutItem(byte[] publicKey, byte[] privateKey, Entry entry, byte[] salt) {
s.dht_put_item(Vectors.bytes2byte_vector(publicKey),
Vectors.bytes2byte_vector(privateKey),
entry.swig(),
Vectors.bytes2byte_vector(salt));
}
/**
* @param infoHash
*/
public void dhtGetPeers(Sha1Hash infoHash) {
s.dht_get_peers(infoHash.swig());
}
/**
* Pausing the session has the same effect as pausing every torrent in
* it, except that torrents will not be resumed by the auto-manage
* mechanism.
*/
public void pause() {
s.pause();
}
/**
* Resuming will restore the torrents to their previous paused
* state. i.e. the session pause state is separate from the torrent pause
* state. A torrent is inactive if it is paused or if the session is
* paused.
*/
public void resume() {
s.resume();
}
public boolean isPaused() {
return s.is_paused();
}
/**
* @param infoHash
* @param port
* @param flags
*/
public void dhtAnnounce(Sha1Hash infoHash, int port, int flags) {
s.dht_announce(infoHash.swig(), port, flags);
}
/**
* @param infoHash
*/
public void dhtAnnounce(Sha1Hash infoHash) {
s.dht_announce(infoHash.swig());
}
/**
* @param endp
* @param entry
* @param userdata
*/
public void dhtDirectRequest(UdpEndpoint endp, Entry entry, long userdata) {
s.dht_direct_request(endp.swig(), entry.swig(), userdata);
}
/**
* @param endp
* @param entry
*/
public void dhtDirectRequest(UdpEndpoint endp, Entry entry) {
s.dht_direct_request(endp.swig(), entry.swig());
}
/**
* returns the port we ended up listening on. Since you
* just pass a port-range to the constructor and to ``listen_on()``, to
* know which port it ended up using, you have to ask the session using
* this function.
*
* @return
*/
public int getListenPort() {
return s.listen_port();
}
public int getSslListenPort() {
return s.ssl_listen_port();
}
/**
* will tell you whether or not the session has
* successfully opened a listening port. If it hasn't, this function will
* return false, and then you can use ``listen_on()`` to make another
* attempt.
*
* @return {@code true} if listening
*/
public boolean isListening() {
return s.is_listening();
}
/**
*
*/
public void addExtension(Plugin plugin) {
SwigPlugin p = new SwigPlugin(plugin);
s.add_extension(p);
p.swigReleaseOwnership();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,58 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.session_params;
/**
* This is a parameters pack for configuring the session
* before it's started.
*
* @author gubatron
* @author aldenml
*/
public class SessionParams {
private final session_params p;
/**
* @param p the native object
*/
public SessionParams(session_params p) {
this.p = p;
}
/**
* This constructor can be used to start with the default plugins
* (ut_metadata, ut_pex and smart_ban). The default values in the
* settings is to start the default features like upnp, nat-pmp,
* and dht for example.
*/
public SessionParams() {
this(new session_params());
}
/**
* This constructor can be used to start with the default plugins
* (ut_metadata, ut_pex and smart_ban). The default values in the
* settings is to start the default features like upnp, nat-pmp,
* and dht for example.
*
* @param settings the initial settings pack
*/
public SessionParams(SettingsPack settings) {
this(new session_params(settings.swig()));
}
/**
* @return the native object
*/
public session_params swig() {
return p;
}
/**
* @return the settings pack
*/
public SettingsPack settings() {
return new SettingsPack(p.getSettings());
}
}

View File

@ -0,0 +1,149 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.alerts.SessionStatsAlert;
/**
* @author gubatron
* @author aldenml
*/
public final class SessionStats {
// these are the channels we keep stats for
private static final int UPLOAD_PAYLOAD = 0;
private static final int UPLOAD_PROTOCOL = 1;
private static final int UPLOAD_IP_PROTOCOL = 2;
private static final int DOWNLOAD_PAYLOAD = 3;
private static final int DOWNLOAD_PROTOCOL = 4;
private static final int DOWNLOAD_IP_PROTOCOL = 5;
private static final int NUM_AVERAGES = 6;
private final Average[] stat;
private long lastTickTime;
private long dhtNodes;
SessionStats() {
this.stat = new Average[NUM_AVERAGES];
for (int i = 0; i < this.stat.length; i++) {
this.stat[i] = new Average();
}
}
public long totalDownload() {
return stat[DOWNLOAD_PAYLOAD].total() +
stat[DOWNLOAD_PROTOCOL].total() +
stat[DOWNLOAD_IP_PROTOCOL].total();
}
public long totalUpload() {
return stat[UPLOAD_PAYLOAD].total() +
stat[UPLOAD_PROTOCOL].total() +
stat[UPLOAD_IP_PROTOCOL].total();
}
public long downloadRate() {
return stat[DOWNLOAD_PAYLOAD].rate() +
stat[DOWNLOAD_PROTOCOL].rate() +
stat[DOWNLOAD_IP_PROTOCOL].rate();
}
public long uploadRate() {
return stat[UPLOAD_PAYLOAD].rate() +
stat[UPLOAD_PROTOCOL].rate() +
stat[UPLOAD_IP_PROTOCOL].rate();
}
public long dhtNodes() {
return dhtNodes;
}
void update(SessionStatsAlert alert) {
long now = System.currentTimeMillis();
long tickIntervalMs = now - lastTickTime;
lastTickTime = now;
long received = alert.value(StatsMetric.NET_RECV_BYTES_COUNTER_INDEX);
long payload = alert.value(StatsMetric.NET_RECV_PAYLOAD_BYTES_COUNTER_INDEX);
long protocol = received - payload;
long ip = alert.value(StatsMetric.NET_RECV_IP_OVERHEAD_BYTES_COUNTER_INDEX);
payload -= stat[DOWNLOAD_PAYLOAD].total();
protocol -= stat[DOWNLOAD_PROTOCOL].total();
ip -= stat[DOWNLOAD_IP_PROTOCOL].total();
stat[DOWNLOAD_PAYLOAD].add(payload);
stat[DOWNLOAD_PROTOCOL].add(protocol);
stat[DOWNLOAD_IP_PROTOCOL].add(ip);
long sent = alert.value(StatsMetric.NET_SENT_BYTES_COUNTER_INDEX);
payload = alert.value(StatsMetric.NET_SENT_PAYLOAD_BYTES_COUNTER_INDEX);
protocol = sent - payload;
ip = alert.value(StatsMetric.NET_SENT_IP_OVERHEAD_BYTES_COUNTER_INDEX);
payload -= stat[UPLOAD_PAYLOAD].total();
protocol -= stat[UPLOAD_PROTOCOL].total();
ip -= stat[UPLOAD_IP_PROTOCOL].total();
stat[UPLOAD_PAYLOAD].add(payload);
stat[UPLOAD_PROTOCOL].add(protocol);
stat[UPLOAD_IP_PROTOCOL].add(ip);
tick(tickIntervalMs);
dhtNodes = alert.value(StatsMetric.DHT_NODES_GAUGE_INDEX);
}
void clear() {
for (int i = 0; i < NUM_AVERAGES; ++i) {
stat[i].clear();
}
dhtNodes = 0;
}
// should be called once every second
private void tick(long tickIntervalMs) {
for (int i = 0; i < NUM_AVERAGES; ++i) {
stat[i].tick(tickIntervalMs);
}
}
private static final class Average {
// total counters
private long totalCounter;
// the accumulator for this second.
private long counter;
// sliding average
private long averageSec5;
public Average() {
}
public void add(long count) {
counter += count;
totalCounter += count;
}
// should be called once every second
public void tick(long tickIntervalMs) {
if (tickIntervalMs >= 1) {
long sample = (counter * 1000) / tickIntervalMs;
averageSec5 = (averageSec5 * 4) / 5 + sample / 5;
counter = 0;
}
}
public long rate() {
return averageSec5;
}
public long total() {
return totalCounter;
}
public void clear() {
counter = 0;
averageSec5 = 0;
totalCounter = 0;
}
}
}

View File

@ -0,0 +1,609 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.settings_pack;
/**
* The ``settings_pack`` struct, contains the names of all settings as
* enum values. These values are passed in to the ``set_str()``,
* ``set_int()``, ``set_bool()`` functions, to specify the setting to
* change.
*
* @author gubatron
* @author aldenml
*/
public final class SettingsPack {
private final settings_pack sp;
public SettingsPack(settings_pack sp) {
this.sp = sp;
}
/**
* Example, how to turn on the DHT using SettingsPack.
* <pre>{@code
* SettingsPack pack = new SettingsPack();
* pack.setBoolean(settings_pack.bool_types.enable_dht.swigValue(), on);
* s.applySettings(pack);
* }</pre>
*/
public SettingsPack() {
this(new settings_pack());
}
/**
* @return
*/
public settings_pack swig() {
return sp;
}
/**
* @param name
* @return
*/
public boolean getBoolean(int name) {
return sp.get_bool(name);
}
/**
* @param name
* @param value
*/
public SettingsPack setBoolean(int name, boolean value) {
sp.set_bool(name, value);
return this;
}
/**
* @param name
* @return
*/
public int getInteger(int name) {
return sp.get_int(name);
}
/**
* @param name
* @param value
*/
public SettingsPack setInteger(int name, int value) {
sp.set_int(name, value);
return this;
}
/**
* @param name
* @return
*/
public String getString(int name) {
return sp.get_str(name);
}
/**
* @param name
* @param value
*/
public SettingsPack setString(int name, String value) {
sp.set_str(name, value);
return this;
}
public void clear() {
sp.clear();
}
public void clear(int name) {
sp.clear(name);
}
/**
* @return the session-global download rate limit in bytes per second. (0 for unlimited)
*/
public int downloadRateLimit() {
return sp.get_int(settings_pack.int_types.download_rate_limit.swigValue());
}
/**
* Sets the session-global limits of download rate limit, in
* bytes per second.
* <p>
* A value of 0 means unlimited.
*
* @param value
*/
public SettingsPack downloadRateLimit(int value) {
sp.set_int(settings_pack.int_types.download_rate_limit.swigValue(), value);
return this;
}
/**
* @return the session-global upload rate limit in bytes per second. (0 for unlimited)
*/
public int uploadRateLimit() {
return sp.get_int(settings_pack.int_types.upload_rate_limit.swigValue());
}
/**
* Sets the session-global limits of upload rate limit, in
* bytes per second.
* <p>
* A value of 0 means unlimited.
*
* @param value
*/
public SettingsPack uploadRateLimit(int value) {
sp.set_int(settings_pack.int_types.upload_rate_limit.swigValue(), value);
return this;
}
/**
* {@code active_downloads} controls how many active
* downloading torrents the queuing mechanism allows.
* <p>
* The target number of active torrents is {@code min(active_downloads +
* active_seeds, active_limit)}. {@code active_downloads} and
* {@code active_seeds} are upper limits on the number of downloading
* torrents and seeding torrents respectively. Setting the value to -1
* means unlimited.
* <p>
* For auto managed torrents, these are the limits they are subject to.
* If there are too many torrents some of the auto managed ones will be
* paused until some slots free up.
* <p>
* You can have more torrents *active*, even though they are not
* announced to the DHT, lsd or their tracker. If some peer knows about
* you for any reason and tries to connect, it will still be accepted,
* unless the torrent is paused, which means it won't accept any
* connections.
* <p>
* For example if there are 10 seeding torrents and 10 downloading
* torrents, and ``active_downloads`` is 4 and ``active_seeds`` is 4,
* there will be 4 seeds active and 4 downloading torrents. If the
* settings are ``active_downloads`` = 2 and ``active_seeds`` = 4, then
* there will be 2 downloading torrents and 4 seeding torrents active.
* Torrents that are not auto managed are also counted against these
* limits. If there are non-auto managed torrents that use up all the
* slots, no auto managed torrent will be activated.
*
* @return
*/
public int activeDownloads() {
return sp.get_int(settings_pack.int_types.active_downloads.swigValue());
}
/**
* @param value
* @see #activeDownloads()
*/
public SettingsPack activeDownloads(int value) {
sp.set_int(settings_pack.int_types.active_downloads.swigValue(), value);
return this;
}
/**
* {@code active_seeds} controls how many active seeding
* torrents the queuing mechanism allows.
*
* @return
* @see #activeDownloads()
*/
public int activeSeeds() {
return sp.get_int(settings_pack.int_types.active_seeds.swigValue());
}
/**
* @param value
* @see #activeSeeds()
*/
public SettingsPack activeSeeds(int value) {
sp.set_int(settings_pack.int_types.active_seeds.swigValue(), value);
return this;
}
/**
* {@code active_checking} is the limit of number of simultaneous checking
* torrents.
*
* @return
* @see #activeDownloads()
*/
public int activeChecking() {
return sp.get_int(settings_pack.int_types.active_checking.swigValue());
}
/**
* @param value
* @see #activeChecking()
*/
public SettingsPack activeChecking(int value) {
sp.set_int(settings_pack.int_types.active_checking.swigValue(), value);
return this;
}
/**
* {@code active_dht_limit} is the max number of torrents to announce to
* the DHT. By default this is set to 88, which is no more than one
* DHT announce every 10 seconds.
*
* @return
* @see #activeDownloads()
*/
public int activeDhtLimit() {
return sp.get_int(settings_pack.int_types.active_dht_limit.swigValue());
}
/**
* @param value
* @see #activeDhtLimit()
*/
public SettingsPack activeDhtLimit(int value) {
sp.set_int(settings_pack.int_types.active_dht_limit.swigValue(), value);
return this;
}
public int dhtUploadRate() {
return sp.get_int(settings_pack.int_types.dht_upload_rate_limit.swigValue());
}
public SettingsPack dhtUploadRate(int value) {
sp.set_int(settings_pack.int_types.dht_upload_rate_limit.swigValue(), value);
return this;
}
/**
* {@code active_tracker_limit} is the max number of torrents to announce
* to their trackers. By default this is 360, which is no more than
* one announce every 5 seconds.
*
* @return
* @see #activeDownloads()
*/
public int activeTrackerLimit() {
return sp.get_int(settings_pack.int_types.active_tracker_limit.swigValue());
}
/**
* @param value
* @see #activeTrackerLimit()
*/
public SettingsPack activeTrackerLimit(int value) {
sp.set_int(settings_pack.int_types.active_tracker_limit.swigValue(), value);
return this;
}
/**
* {@code active_lsd_limit} is the max number of torrents to announce to
* the local network over the local service discovery protocol. By
* default this is 80, which is no more than one announce every 5
* seconds (assuming the default announce interval of 5 minutes).
*
* @return
* @see #activeDownloads()
*/
public int activeLsdLimit() {
return sp.get_int(settings_pack.int_types.active_lsd_limit.swigValue());
}
/**
* @param value
* @see #activeLsdLimit()
*/
public SettingsPack activeLsdLimit(int value) {
sp.set_int(settings_pack.int_types.active_lsd_limit.swigValue(), value);
return this;
}
/**
* {@code active_limit} is a hard limit on the number of active (auto
* managed) torrents. This limit also applies to slow torrents.
*
* @return the value
* @see #activeDownloads()
*/
public int activeLimit() {
return sp.get_int(settings_pack.int_types.active_limit.swigValue());
}
/**
* {@code active_limit} is a hard limit on the number of active (auto
* managed) torrents. This limit also applies to slow torrents.
*
* @param value the value
* @see #activeLimit()
*/
public SettingsPack activeLimit(int value) {
sp.set_int(settings_pack.int_types.active_limit.swigValue(), value);
return this;
}
/**
* @return global limit on the number of connections opened.
*/
public int connectionsLimit() {
return sp.get_int(settings_pack.int_types.connections_limit.swigValue());
}
/**
* Sets a global limit on the number of connections opened. The number of
* connections is set to a hard minimum of at least two per torrent, so
* if you set a too low connections limit, and open too many torrents,
* the limit will not be met.
*
* @param value
*/
public SettingsPack connectionsLimit(int value) {
sp.set_int(settings_pack.int_types.connections_limit.swigValue(), value);
return this;
}
/**
* @return the maximum number of peers in the list of known peers. (0 for unlimited)
*/
public int maxPeerlistSize() {
return sp.get_int(settings_pack.int_types.max_peerlist_size.swigValue());
}
/**
* Sets the maximum number of peers in the list of known peers. These peers
* are not necessarily connected, so this number should be much greater
* than the maximum number of connected peers. Peers are evicted from the
* cache when the list grows passed 90% of this limit, and once the size
* hits the limit, peers are no longer added to the list. If this limit
* is set to 0, there is no limit on how many peers we'll keep in the
* peer list.
*
* @param value
*/
public SettingsPack maxPeerlistSize(int value) {
sp.set_int(settings_pack.int_types.max_peerlist_size.swigValue(), value);
return this;
}
/**
* @return the maximum number of bytes a connection may have pending in the disk
* write queue before its download rate is being throttled.
*/
public int maxQueuedDiskBytes() {
return sp.get_int(settings_pack.int_types.max_queued_disk_bytes.swigValue());
}
/**
* Sets the maximum number of bytes a connection may have pending in the disk
* write queue before its download rate is being throttled. This prevents
* fast downloads to slow medias to allocate more memory indefinitely.
* This should be set to at least 16 kB to not completely disrupt normal
* downloads. If it's set to 0, you will be starving the disk thread and
* nothing will be written to disk. this is a per session setting.
* <p>
* When this limit is reached, the peer connections will stop reading
* data from their sockets, until the disk thread catches up. Setting
* this too low will severely limit your download rate.
*
* @param value
*/
public SettingsPack maxQueuedDiskBytes(int value) {
sp.set_int(settings_pack.int_types.max_queued_disk_bytes.swigValue(), value);
return this;
}
/**
* @return the upper limit of the send buffer low-watermark.
*/
public int sendBufferWatermark() {
return sp.get_int(settings_pack.int_types.send_buffer_watermark.swigValue());
}
/**
* Sets the upper limit of the send buffer low-watermark.
* <p>
* if the send buffer has fewer bytes than this, we'll read another 16kB
* block onto it. If set too small, upload rate capacity will suffer. If
* set too high, memory will be wasted. The actual watermark may be lower
* than this in case the upload rate is low, this is the upper limit.
*
* @param value
*/
public SettingsPack sendBufferWatermark(int value) {
sp.set_int(settings_pack.int_types.send_buffer_watermark.swigValue(), value);
return this;
}
/**
* Sets the disk write and read cache. It is specified in units of 16 KiB
* blocks. Buffers that are part of a peer's send or receive buffer also
* count against this limit. Send and receive buffers will never be
* denied to be allocated, but they will cause the actual cached blocks
* to be flushed or evicted. If this is set to -1, the cache size is
* automatically set to the amount of physical RAM available in the
* machine divided by 8. If the amount of physical RAM cannot be
* determined, it's set to 1024 (= 16 MiB).
*
* @return the current value
*/
public int cacheSize() {
return sp.get_int(settings_pack.int_types.cache_size.swigValue());
}
/**
* Sets the disk write and read cache. It is specified in units of 16 KiB
* blocks. Buffers that are part of a peer's send or receive buffer also
* count against this limit. Send and receive buffers will never be
* denied to be allocated, but they will cause the actual cached blocks
* to be flushed or evicted. If this is set to -1, the cache size is
* automatically set to the amount of physical RAM available in the
* machine divided by 8. If the amount of physical RAM cannot be
* determined, it's set to 1024 (= 16 MiB).
*
* @param value the new value
* @return this
*/
public SettingsPack cacheSize(int value) {
sp.set_int(settings_pack.int_types.cache_size.swigValue(), value);
return this;
}
/**
* @return
*/
public int tickInterval() {
return sp.get_int(settings_pack.int_types.tick_interval.swigValue());
}
/**
* Specifies the number of milliseconds between internal ticks. This is
* the frequency with which bandwidth quota is distributed to peers. It
* should not be more than one second (i.e. 1000 ms). Setting this to a
* low value (around 100) means higher resolution bandwidth quota
* distribution, setting it to a higher value saves CPU cycles.
*
* @param value
*/
public SettingsPack tickInterval(int value) {
sp.set_int(settings_pack.int_types.tick_interval.swigValue(), value);
return this;
}
/**
* @return
*/
public int inactivityTimeout() {
return sp.get_int(settings_pack.int_types.inactivity_timeout.swigValue());
}
/**
* if a peer is uninteresting and uninterested for longer than this
* number of seconds, it will be disconnected. default is 10 minutes
*
* @param value
*/
public SettingsPack inactivityTimeout(int value) {
sp.set_int(settings_pack.int_types.inactivity_timeout.swigValue(), value);
return this;
}
/**
* @return
*/
public boolean seedingOutgoingConnections() {
return sp.get_bool(settings_pack.bool_types.seeding_outgoing_connections.swigValue());
}
/**
* Determines if seeding (and finished) torrents should attempt to make
* outgoing connections or not. By default this is true. It may be set to
* false in very specific applications where the cost of making outgoing
* connections is high, and there are no or small benefits of doing so.
* For instance, if no nodes are behind a firewall or a NAT, seeds don't
* need to make outgoing connections.
*
* @param value
*/
public SettingsPack seedingOutgoingConnections(boolean value) {
sp.set_bool(settings_pack.bool_types.seeding_outgoing_connections.swigValue(), value);
return this;
}
/**
* @return
*/
public boolean anonymousMode() {
return sp.get_bool(settings_pack.bool_types.anonymous_mode.swigValue());
}
/**
* defaults to false. When set to true, the client tries to hide its
* identity to a certain degree. The peer-ID will no longer include the
* client's fingerprint. The user-agent will be reset to an empty string.
* It will also try to not leak other identifying information, such as
* your local listen port, your IP etc.
* <p>
* If you're using I2P, a VPN or a proxy, it might make sense to enable
* anonymous mode.
*
* @param value
*/
public SettingsPack anonymousMode(boolean value) {
sp.set_bool(settings_pack.bool_types.anonymous_mode.swigValue(), value);
return this;
}
/**
* @return
*/
public boolean enableDht() {
return sp.get_bool(settings_pack.bool_types.enable_dht.swigValue());
}
/**
* Starts the dht node and makes the trackerless service available to
* torrents.
*
* @param value
* @return this
*/
public SettingsPack enableDht(boolean value) {
sp.set_bool(settings_pack.bool_types.enable_dht.swigValue(), value);
return this;
}
/**
* @return
*/
public String listenInterfaces() {
return sp.get_str(settings_pack.string_types.listen_interfaces.swigValue());
}
/**
* @param value
* @return this
*/
public SettingsPack listenInterfaces(String value) {
sp.set_str(settings_pack.string_types.listen_interfaces.swigValue(), value);
return this;
}
/**
* @return the current value
* @see #stopTrackerTimeout(int)
*/
public int stopTrackerTimeout() {
return sp.get_int(settings_pack.int_types.stop_tracker_timeout.swigValue());
}
/**
* {@code stop_tracker_timeout} is the number of seconds to wait when
* sending a stopped message before considering a tracker to have
* timed out. This is usually shorter, to make the client quit faster.
* If the value is set to 0, the connections to trackers with the
* stopped event are suppressed.
*
* @param value the new value
* @return this
*/
public SettingsPack stopTrackerTimeout(int value) {
sp.set_int(settings_pack.int_types.stop_tracker_timeout.swigValue(), value);
return this;
}
/**
* @return the current value
* @see #alertQueueSize(int)
*/
public int alertQueueSize() {
return sp.get_int(settings_pack.int_types.alert_queue_size.swigValue());
}
/**
* {@code alert_queue_size} is the maximum number of alerts queued up
* internally. If alerts are not popped, the queue will eventually
* fill up to this level.
*
* @param value the new value
* @return this
*/
public SettingsPack alertQueueSize(int value) {
sp.set_int(settings_pack.int_types.alert_queue_size.swigValue(), value);
return this;
}
}

View File

@ -0,0 +1,174 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.sha1_hash;
import com.frostwire.jlibtorrent.swig.sha1_hash_vector;
import java.util.ArrayList;
/**
* This type holds a SHA-1 digest or any other kind of 20 byte
* sequence. It implements a number of convenience functions, such
* as bit operations, comparison operators etc.
* <p>
* In libtorrent it is primarily used to hold info-hashes, piece-hashes,
* peer IDs, node IDs etc.
*
* @author gubatron
* @author aldenml
*/
public final class Sha1Hash implements Comparable<Sha1Hash>, Cloneable {
private final sha1_hash h;
/**
* @param h native object
*/
public Sha1Hash(sha1_hash h) {
this.h = h;
}
/**
* @param bytes hash as an array of bytes
*/
public Sha1Hash(byte[] bytes) {
if (bytes.length != 20) {
throw new IllegalArgumentException("bytes array must be of length 20");
}
this.h = new sha1_hash(Vectors.bytes2byte_vector(bytes));
}
/**
* @param hex hex coded representation of the hash
*/
public Sha1Hash(String hex) {
this(Hex.decode(hex));
}
/**
* Constructs an all-zero sha1-hash
*/
public Sha1Hash() {
this(new sha1_hash());
}
/**
* @return the native object
*/
public sha1_hash swig() {
return h;
}
/**
* set the sha1-hash to all zeroes.
*/
public void clear() {
h.clear();
}
/**
* return true if the sha1-hash is all zero.
*
* @return true if zero
*/
public boolean isAllZeros() {
return h.is_all_zeros();
}
/**
* @return the number of leading zeroes
*/
public int countLeadingZeroes() {
return h.count_leading_zeroes();
}
/**
* Returns the hex representation of this has.
* <p>
* This method uses internally the libtorrent to_hex function.
*
* @return the hex representation
*/
public String toHex() {
return h.to_hex();
}
/**
* @param o {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
public int compareTo(Sha1Hash o) {
return sha1_hash.compare(this.h, o.h);
}
/**
* Returns an hex representation of this hash. Internally it
* calls {@link #toHex()}.
*
* @return {@inheritDoc}
*/
@Override
public String toString() {
return toHex();
}
/**
* @param obj {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Sha1Hash)) {
return false;
}
return h.op_eq(((Sha1Hash) obj).h);
}
/**
* @return {@inheritDoc}
*/
@Override
public int hashCode() {
return h.hash_code();
}
@Override
public Sha1Hash clone() {
return new Sha1Hash(new sha1_hash(h));
}
/**
* returns an all-F sha1-hash. i.e. the maximum value
* representable by a 160 bit number (20 bytes). This is
* a static member function.
*
* @return the maximum number
*/
public static Sha1Hash max() {
return new Sha1Hash(sha1_hash.max());
}
/**
* returns an all-zero sha1-hash. i.e. the minimum value
* representable by a 160 bit number (20 bytes). This is
* a static member function.
*
* @return the minimum number (zero)
*/
public static Sha1Hash min() {
return new Sha1Hash(sha1_hash.min());
}
static ArrayList<Sha1Hash> convert(sha1_hash_vector v) {
int size = (int) v.size();
ArrayList<Sha1Hash> l = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
l.add(new Sha1Hash(v.get(i)));
}
return l;
}
}

View File

@ -0,0 +1,62 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.metric_type_t;
import com.frostwire.jlibtorrent.swig.stats_metric;
/**
* Describes one statistics metric from the session.
*
* @author gubatron
* @author aldenml
*/
public final class StatsMetric {
public static final String NET_SENT_PAYLOAD_BYTES_COUNTER_NAME = "net.sent_payload_bytes";
public static final String NET_SENT_BYTES_COUNTER_NAME = "net.sent_bytes";
public static final String NET_SENT_IP_OVERHEAD_BYTES_COUNTER_NAME = "net.sent_ip_overhead_bytes";
public static final String NET_RECV_PAYLOAD_BYTES_COUNTER_NAME = "net.recv_payload_bytes";
public static final String NET_RECV_BYTES_COUNTER_NAME = "net.recv_bytes";
public static final String NET_RECV_IP_OVERHEAD_BYTES_COUNTER_NAME = "net.recv_ip_overhead_bytes";
public static final int NET_SENT_PAYLOAD_BYTES_COUNTER_INDEX = LibTorrent.findMetricIdx(NET_SENT_PAYLOAD_BYTES_COUNTER_NAME);
public static final int NET_SENT_BYTES_COUNTER_INDEX = LibTorrent.findMetricIdx(NET_SENT_BYTES_COUNTER_NAME);
public static final int NET_SENT_IP_OVERHEAD_BYTES_COUNTER_INDEX = LibTorrent.findMetricIdx(NET_SENT_IP_OVERHEAD_BYTES_COUNTER_NAME);
public static final int NET_RECV_PAYLOAD_BYTES_COUNTER_INDEX = LibTorrent.findMetricIdx(NET_RECV_PAYLOAD_BYTES_COUNTER_NAME);
public static final int NET_RECV_BYTES_COUNTER_INDEX = LibTorrent.findMetricIdx(NET_RECV_BYTES_COUNTER_NAME);
public static final int NET_RECV_IP_OVERHEAD_BYTES_COUNTER_INDEX = LibTorrent.findMetricIdx(NET_RECV_IP_OVERHEAD_BYTES_COUNTER_NAME);
public static final String DHT_NODES_GAUGE_NAME = "dht.dht_nodes";
public static final int DHT_NODES_GAUGE_INDEX = LibTorrent.findMetricIdx(DHT_NODES_GAUGE_NAME);
public static final int TYPE_COUNTER = metric_type_t.counter.swigValue();
public static final int TYPE_GAUGE = metric_type_t.gauge.swigValue();
StatsMetric(stats_metric sm) {
this.name = sm.get_name();
this.valueIndex = sm.getValue_index();
this.type = sm.getType().swigValue();
}
public final String name;
public final int valueIndex;
public final int type;
@Override
public String toString() {
return name + ":" + valueIndex + ":" + typeStr();
}
private String typeStr() {
String str = "unknown";
if (type == TYPE_COUNTER) {
str = "counter";
} else if (type == TYPE_GAUGE) {
str = "gauge";
}
return str;
}
}

View File

@ -0,0 +1,57 @@
package com.frostwire.jlibtorrent;
import com.frostwire.jlibtorrent.swig.storage_mode_t;
/**
* Types of storage allocation used for {@link AddTorrentParams#storageMode(StorageMode)}.
*
* @author gubatron
* @author aldenml
*/
public enum StorageMode {
/**
* All pieces will be written to their final position, all files will be
* allocated in full when the torrent is first started. This is done with
* {@code fallocate()} and similar calls. This mode minimizes fragmentation.
*/
STORAGE_MODE_ALLOCATE(storage_mode_t.storage_mode_allocate.swigValue()),
/**
* All pieces will be written to the place where they belong and sparse files
* will be used. This is the recommended, and default mode.
*/
STORAGE_MODE_SPARSE(storage_mode_t.storage_mode_sparse.swigValue()),
/**
*
*/
UNKNOWN(-1);
StorageMode(int swigValue) {
this.swigValue = swigValue;
}
private final int swigValue;
/**
* @return the native value
*/
public int swig() {
return swigValue;
}
/**
* @param swigValue the native value
* @return the java enum
*/
public static StorageMode fromSwig(int swigValue) {
StorageMode[] enumValues = StorageMode.class.getEnumConstants();
for (StorageMode ev : enumValues) {
if (ev.swig() == swigValue) {
return ev;
}
}
return UNKNOWN;
}
}

Some files were not shown because too many files have changed in this diff Show More