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

47 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
2344fe0942 Merge branch 'l10n_develop' into 'develop'
New Crowdin updates

See merge request tom79/fedilab-tube!63
2021-01-09 18:47:38 +01:00
7c309b68b8 New Crowdin updates 2021-01-09 18:47:38 +01:00
c8c5e56a17 Cannot comment reply with Pleroma accounts 2021-01-09 18:47:08 +01:00
cef227ba42 Release notes 2021-01-09 17:38:25 +01:00
02296b038a Release 1.12.0 2021-01-09 17:33:53 +01:00
b76a4cfcf5 Fix issue #160 #87 #88 - Notification counter + mark them all as read 2021-01-09 17:18:32 +01:00
f168f101bc Fix issue #160 - Add a notification counter 2021-01-09 14:13:34 +01:00
2e8a86fe20 Fix issue #160 - Move account item to make it visible in top bar 2021-01-09 10:52:34 +01:00
fe0d2fe726 fix gradle issue 2021-01-09 10:48:11 +01:00
9b322cc922 Fix issue #165 2021-01-09 10:42:37 +01:00
346656e53d Fix issue #167 2021-01-09 09:21:26 +01:00
6e6187175a Merge branch 'l10n_develop' into 'develop'
New Crowdin updates

See merge request tom79/fedilab-tube!62
2021-01-08 18:08:28 +01:00
5e832fa046 New Crowdin updates 2021-01-08 18:08:28 +01:00
34007d4507 remove signingConfigs 2021-01-08 17:57:26 +01:00
fd400f025e Merge branch 'donation_google' into develop
# Conflicts:
#	.gitignore
2021-01-08 17:45:00 +01:00
f3f474ee13 Some fixes 2021-01-08 17:44:32 +01:00
d971032d52 Some changes 2021-01-08 11:29:49 +01:00
961c77103e Some changes 2021-01-08 11:18:01 +01:00
b22b21c47a Some changes 2021-01-07 17:39:47 +01:00
6d70bd758a Some changes 2021-01-06 19:23:44 +01:00
087ac92f15 gitignore 2021-01-06 10:30:05 +01:00
99fe789f30 Fix issue #164 & #156 2021-01-05 18:37:58 +01:00
10892f92f1 Fix issue #162 2021-01-05 17:41:38 +01:00
0a919c85ab Fix issue #163 2021-01-05 17:37:27 +01:00
761abc013f Allow donation through Google 2021-01-05 17:26:21 +01:00
506 changed files with 123801 additions and 817 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 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:

View File

@ -1,6 +1,8 @@
apply plugin: 'com.android.application'
apply plugin: "androidx.navigation.safeargs"
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
@ -9,8 +11,8 @@ android {
minSdkVersion 21
targetSdkVersion 30
versionCode 34
versionName "1.11.0"
versionCode 39
versionName "1.13.1"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -40,7 +42,7 @@ android {
}
//boolean full_instances if set to false means TubeAcad
productFlavors {
fdroid_peertube_apps_educ {
fdroid_acad {
applicationId "app.fedilab.fedilabtube"
resValue "string", "app_name", "TubeAcad"
resValue "string", "app_id", "app.fedilab.fedilabtube"
@ -51,8 +53,11 @@ android {
buildConfigField "boolean", "sepia_search", "false"
buildConfigField "boolean", "instance_switcher", "true"
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"
resValue "string", "app_name", "TubeAcad"
resValue "string", "app_id", "app.fedilab.fedilabtube"
@ -63,6 +68,9 @@ android {
buildConfigField "boolean", "sepia_search", "false"
buildConfigField "boolean", "instance_switcher", "true"
buildConfigField "boolean", "allow_remote_connections", "false"
buildConfigField "boolean", "google_cast_lib", "true"
buildConfigField "int", "cast_enabled", "1"
buildConfigField "int", "default_theme", "2"
}
fdroid_full {
applicationId "app.fedilab.tubelab"
@ -75,6 +83,9 @@ android {
buildConfigField "boolean", "sepia_search", "true"
buildConfigField "boolean", "instance_switcher", "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 {
applicationId "app.fedilab.tubelab"
@ -87,6 +98,9 @@ android {
buildConfigField "boolean", "sepia_search", "true"
buildConfigField "boolean", "instance_switcher", "true"
buildConfigField "boolean", "allow_remote_connections", "true"
buildConfigField "boolean", "google_cast_lib", "true"
buildConfigField "int", "cast_enabled", "1"
buildConfigField "int", "default_theme", "2"
}
queermotion {
applicationId "org.queermotion.peertube"
@ -99,6 +113,9 @@ android {
buildConfigField "boolean", "sepia_search", "false"
buildConfigField "boolean", "instance_switcher", "false"
buildConfigField "boolean", "allow_remote_connections", "false"
buildConfigField "boolean", "google_cast_lib", "false"
buildConfigField "int", "cast_enabled", "0"
buildConfigField "int", "default_theme", "2"
}
bittube {
applicationId "app.fedilab.bittube"
@ -111,33 +128,37 @@ android {
buildConfigField "boolean", "sepia_search", "false"
buildConfigField "boolean", "instance_switcher", "true"
buildConfigField "boolean", "allow_remote_connections", "false"
buildConfigField "boolean", "google_cast_lib", "true"
buildConfigField "int", "cast_enabled", "1"
buildConfigField "int", "default_theme", "1"
}
}
sourceSets {
fdroid_peertube_apps_educ {
res.srcDirs = ['src/main/res', 'src/acad/res']
java.srcDirs = ['src/main/java', 'src/acad/java']
fdroid_acad {
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 {
res.srcDirs = ['src/main/res', 'src/acad/res']
java.srcDirs = ['src/main/java', 'src/acad/java']
google_acad {
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', 'src/google_cast_lib/java']
}
fdroid_full {
res.srcDirs = ['src/main/res', 'src/full/res']
java.srcDirs = ['src/main/java', 'src/full/java']
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', 'src/no_google_cast_lib/java']
}
google_full {
res.srcDirs = ['src/main/res', 'src/full/res']
java.srcDirs = ['src/main/java', 'src/full/java']
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', 'src/google_cast_lib/java']
}
queermotion {
res.srcDirs = ['src/main/res', 'src/queermotion/res']
java.srcDirs = ['src/main/java', 'src/full/java']
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', 'src/no_google_cast_lib/java']
}
bittube {
res.srcDirs = ['src/main/res', 'src/bittube/res']
java.srcDirs = ['src/main/java', 'src/full/java']
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', 'src/google_cast_lib/java']
}
}
}
@ -166,6 +187,7 @@ dependencies {
implementation 'androidx.browser:browser:1.3.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation project(path: ':torrentStream')
implementation project(path: ':frostwire-jlibtorrent')
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
@ -177,6 +199,7 @@ dependencies {
implementation "com.github.mabbas007:TagsEditText:1.0.5"
implementation "com.github.bumptech.glide:glide: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 "net.gotev:uploadservice:4.5.1"
implementation "net.gotev:uploadservice-okhttp:4.5.1"
@ -194,15 +217,34 @@ dependencies {
implementation "androidx.work:work-runtime: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"
fdroid_peertube_apps_educImplementation 'org.matomo.sdk:tracker:4.1.2'
google_peertube_apps_educImplementation 'org.matomo.sdk:tracker:4.1.2'
//************ MATOMO --> acad instances only **************//
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

@ -0,0 +1,278 @@
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.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.android.billingclient.api.AcknowledgePurchaseParams;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetailsParams;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;
import org.jetbrains.annotations.NotNull;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import app.fedilab.fedilabtube.databinding.ActivityDonationBinding;
import app.fedilab.fedilabtube.fragment.MySubscriptionDonationsFragment;
import app.fedilab.fedilabtube.fragment.DonationsFragment;
public class DonationActivity extends AppCompatActivity implements PurchasesUpdatedListener {
DonationsFragment donationsFragment;
DonationsFragment subscriptionDonationsFragment;
MySubscriptionDonationsFragment mySubscriptionDonationsFragment;
private ActivityDonationBinding binding;
private BillingClient billingClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityDonationBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
billingClient = BillingClient.newBuilder(this)
.setListener(this)
.enablePendingPurchases()
.build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NotNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
donationsFragment.initialized(billingClient);
subscriptionDonationsFragment.initialized(billingClient);
List<Purchase> purchases = queryPurchases();
if (purchases != null) {
for (Purchase purchase : purchases) {
if (!purchase.isAutoRenewing()) {
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
ConsumeResponseListener listener = (billingResult1, purchaseToken) -> {
//noinspection StatementWithEmptyBody
if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// Handle the success of the consume operation.
}
};
billingClient.consumeAsync(consumeParams, listener);
}
}
}
}
}
@Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
});
if (getSupportActionBar() != null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
donationsFragment = new DonationsFragment();
Bundle bundle1 = new Bundle();
bundle1.putSerializable("isSubscriptions", false);
donationsFragment.setArguments(bundle1);
subscriptionDonationsFragment = new DonationsFragment();
Bundle bundle2 = new Bundle();
bundle2.putSerializable("isSubscriptions", true);
subscriptionDonationsFragment.setArguments(bundle2);
mySubscriptionDonationsFragment = new MySubscriptionDonationsFragment();
binding.tablayout.addTab(binding.tablayout.newTab().setText(getString(R.string.one_time)));
binding.tablayout.addTab(binding.tablayout.newTab().setText(getString(R.string.subscriptions)));
binding.tablayout.addTab(binding.tablayout.newTab().setText(getString(R.string.my_subscriptions)));
binding.viewpager.setOffscreenPageLimit(3);
PagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
binding.viewpager.setAdapter(mPagerAdapter);
binding.viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
TabLayout.Tab tab = binding.tablayout.getTabAt(position);
if (tab != null)
tab.select();
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
binding.tablayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
binding.viewpager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
private List<Purchase> queryPurchases() {
Purchase.PurchasesResult purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
List<Purchase> purchases = purchasesResult.getPurchasesList();
List<String> isSubscriptions = new ArrayList<>();
HashMap<String, Purchase> map = new HashMap<>();
if (purchases != null) {
for (Purchase purchase : purchases) {
try {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
JSONObject purchaseJson = new JSONObject(purchase.getOriginalJson());
String productId = purchaseJson.getString("productId");
isSubscriptions.add(productId);
map.put(productId, purchase);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
SkuDetailsParams.Builder paramsSub = SkuDetailsParams.newBuilder();
paramsSub.setSkusList(isSubscriptions).setType(BillingClient.SkuType.SUBS);
billingClient.querySkuDetailsAsync(paramsSub.build(),
(billingResult2, skuDetailsList) -> mySubscriptionDonationsFragment.initialized(skuDetailsList, map, billingClient));
} else {
mySubscriptionDonationsFragment.initialized(new ArrayList<>(), map, billingClient);
}
return purchases;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> purchases) {
String message;
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
&& purchases != null) {
for (Purchase purchase : purchases) {
if (!purchase.isAutoRenewing()) {
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
ConsumeResponseListener listener = (billingResult1, purchaseToken) -> {
};
billingClient.consumeAsync(consumeParams, listener);
} else {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.acknowledgePurchase(acknowledgePurchaseParams, b -> {
});
}
}
queryPurchases();
}
}
message = getString(R.string.donation_succeeded_null);
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
message = getString(R.string.donation_cancelled);
} else {
message = getString(R.string.toast_error);
}
View parentLayout = findViewById(android.R.id.content);
Snackbar snackbar = Snackbar.make(parentLayout, message, Snackbar.LENGTH_INDEFINITE);
snackbar.setAction(R.string.close, view -> snackbar.dismiss());
snackbar.show();
}
/**
* Pager adapter for the 2 fragments
*/
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
@NotNull
@Override
public Fragment getItem(int position) {
if (position == 0) {
return donationsFragment;
} else if (position == 1) {
return subscriptionDonationsFragment;
} else {
return mySubscriptionDonationsFragment;
}
}
@Override
public int getCount() {
return 3;
}
}
}

View File

@ -0,0 +1,91 @@
package app.fedilab.fedilabtube.drawable;
/* 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 android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.SkuDetails;
import java.util.List;
import java.util.Locale;
import app.fedilab.fedilabtube.DonationActivity;
import app.fedilab.fedilabtube.R;
import app.fedilab.fedilabtube.databinding.DrawerDonationBinding;
public class DonationButtonAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final List<SkuDetails> skuDetails;
private final BillingClient billingClient;
private Context context;
private final boolean isSubscription;
public DonationButtonAdapter(List<SkuDetails> skuDetails, BillingClient billingClient, boolean subscription) {
this.isSubscription = subscription;
this.skuDetails = skuDetails;
this.billingClient = billingClient;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
DrawerDonationBinding itemBinding = DrawerDonationBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(itemBinding);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
final ViewHolder holder = (ViewHolder) viewHolder;
SkuDetails skuDetail = skuDetails.get(position);
String currency = skuDetail.getPriceCurrencyCode();
String price = skuDetail.getPrice();
if (isSubscription) {
holder.binding.buttonDonation.setText(String.format(Locale.getDefault(), "%s %s / %s", price, currency, context.getString(R.string.month)));
} else {
holder.binding.buttonDonation.setText(String.format(Locale.getDefault(), "%s %s", price, currency));
}
holder.binding.buttonDonation.setOnClickListener(v -> {
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetail)
.build();
billingClient.launchBillingFlow((DonationActivity) context, billingFlowParams);
});
}
@Override
public int getItemCount() {
return skuDetails.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
DrawerDonationBinding binding;
ViewHolder(DrawerDonationBinding itemView) {
super(itemView.getRoot());
binding = itemView;
}
}
}

View File

@ -0,0 +1,128 @@
package app.fedilab.fedilabtube.drawable;
/* 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 android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.SkuDetails;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.List;
import app.fedilab.fedilabtube.R;
import app.fedilab.fedilabtube.databinding.DrawerMyDonationBinding;
import es.dmoral.toasty.Toasty;
public class DonationHistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final List<SkuDetails> skuDetailsList;
private final BillingClient billingClient;
private Context context;
private final HashMap<String, Purchase> map;
public DonationHistoryAdapter(List<SkuDetails> SkuDetailsList, HashMap<String, Purchase> map, BillingClient billingClient) {
this.skuDetailsList = SkuDetailsList;
this.billingClient = billingClient;
this.map = map;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
DrawerMyDonationBinding itemBinding = DrawerMyDonationBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(itemBinding);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
final ViewHolder holder = (ViewHolder) viewHolder;
SkuDetails skuDetails = skuDetailsList.get(position);
holder.binding.productTitle.setText(skuDetails.getTitle());
holder.binding.productName.setText(skuDetails.getDescription());
holder.binding.productInfo.setText(skuDetails.getOriginalPrice());
holder.binding.cancelDonation.setOnClickListener(v -> {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
dialogBuilder.setMessage(R.string.cancel_subscription_confirm);
dialogBuilder.setPositiveButton(R.string.cancel_subscription, (dialog, id) -> {
JSONObject skudetailsJson;
try {
skudetailsJson = new JSONObject(skuDetails.getOriginalJson());
String productId = skudetailsJson.getString("productId");
if (map.containsKey(productId)) {
Purchase purchase = map.get(productId);
if (purchase != null) {
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
ConsumeResponseListener listener = (billingResult1, purchaseToken) -> {
if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK) {
Toasty.success(context, context.getString(R.string.subscription_cancelled), Toasty.LENGTH_LONG).show();
}
skuDetailsList.remove(skuDetails);
notifyDataSetChanged();
};
billingClient.consumeAsync(consumeParams, listener);
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
dialog.dismiss();
});
dialogBuilder.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss());
AlertDialog alertDialogLogoutAccount = dialogBuilder.create();
alertDialogLogoutAccount.show();
});
}
@Override
public int getItemCount() {
return skuDetailsList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
DrawerMyDonationBinding binding;
ViewHolder(DrawerMyDonationBinding itemView) {
super(itemView.getRoot());
binding = itemView;
}
}
}

View File

@ -0,0 +1,112 @@
package app.fedilab.fedilabtube.fragment;
/* 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 android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.SkuDetailsParams;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import app.fedilab.fedilabtube.R;
import app.fedilab.fedilabtube.databinding.FragmentDonationsBinding;
import app.fedilab.fedilabtube.drawable.DonationButtonAdapter;
public class DonationsFragment extends Fragment {
public static final String[] donations = {"1", "2", "5", "10"};
private FragmentDonationsBinding binding;
private View rootView;
private Context context;
private boolean isSubscriptions;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentDonationsBinding.inflate(LayoutInflater.from(context));
rootView = binding.getRoot();
context = getContext();
Bundle bundle = this.getArguments();
if (bundle != null) {
isSubscriptions = bundle.getBoolean("isSubscriptions", false);
}
int donationText;
if (isSubscriptions) {
donationText = R.string.recurrent_donation_text;
} else {
donationText = R.string.one_time_donation_text;
}
binding.donationText.setText(donationText);
binding.loader.setVisibility(View.VISIBLE);
binding.lvProducts.setVisibility(View.GONE);
return rootView;
}
public void initialized(BillingClient bc) {
List<String> donationsList = new ArrayList<>();
for (String val : donations) {
donationsList.add("tubelab_donation_" + val + (isSubscriptions ? "_s" : ""));
}
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
if (isSubscriptions) {
params.setSkusList(donationsList).setType(BillingClient.SkuType.SUBS);
} else {
params.setSkusList(donationsList).setType(BillingClient.SkuType.INAPP);
}
bc.querySkuDetailsAsync(params.build(),
(billingResult, skuDetailsList) -> {
binding.loader.setVisibility(View.GONE);
binding.lvProducts.setVisibility(View.VISIBLE);
if (skuDetailsList != null) {
Collections.sort(skuDetailsList, (obj1, obj2) -> obj1.getPrice().compareTo(obj2.getPrice()));
}
DonationButtonAdapter donationButtonAdapter = new DonationButtonAdapter(skuDetailsList, bc, isSubscriptions);
binding.lvProducts.setAdapter(donationButtonAdapter);
binding.lvProducts.setLayoutManager(new LinearLayoutManager(context));
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
rootView = null;
}
@Override
public void onCreate(Bundle saveInstance) {
super.onCreate(saveInstance);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
this.context = context;
}
}

View File

@ -0,0 +1,80 @@
package app.fedilab.fedilabtube.fragment;
/* 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 android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.SkuDetails;
import java.util.HashMap;
import java.util.List;
import app.fedilab.fedilabtube.databinding.FragmentMyDonationsBinding;
import app.fedilab.fedilabtube.drawable.DonationHistoryAdapter;
public class MySubscriptionDonationsFragment extends Fragment {
private FragmentMyDonationsBinding binding;
private View rootView;
private Context context;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentMyDonationsBinding.inflate(LayoutInflater.from(context));
rootView = binding.getRoot();
context = getContext();
binding.loader.setVisibility(View.VISIBLE);
binding.lvPurchases.setVisibility(View.GONE);
return rootView;
}
public void initialized(List<SkuDetails> skuDetailsList, HashMap<String, Purchase> map, BillingClient bc) {
binding.loader.setVisibility(View.GONE);
binding.lvPurchases.setVisibility(View.VISIBLE);
DonationHistoryAdapter donationHistoryAdapter = new DonationHistoryAdapter(skuDetailsList, map, bc);
binding.lvPurchases.setAdapter(donationHistoryAdapter);
binding.lvPurchases.setLayoutManager(new LinearLayoutManager(context));
}
@Override
public void onDestroyView() {
super.onDestroyView();
rootView = null;
}
@Override
public void onCreate(Bundle saveInstance) {
super.onCreate(saveInstance);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
this.context = context;
}
}

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?><!--
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>.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ShowChannelActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:theme="@style/ThemeOverlay.AppCompat.ActionBar">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
</com.google.android.material.appbar.CollapsingToolbarLayout>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="fill"
app:tabMode="fixed"
app:tabSelectedTextColor="?colorAccent"
app:tabTextColor="@android:color/white" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,23 @@
<?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:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/button_donation"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:paddingStart="40dp"
android:paddingTop="15dp"
android:paddingEnd="40dp"
android:paddingBottom="15dp"
android:textSize="25sp"
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,54 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<TextView
android:id="@+id/product_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/cancel_donation"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/product_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toStartOf="@+id/cancel_donation"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/product_title" />
<TextView
android:id="@+id/product_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toStartOf="@+id/cancel_donation"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/product_name" />
<ImageButton
android:id="@+id/cancel_donation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/cancel"
android:src="@drawable/ic_baseline_delete_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?><!--
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>.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/fab_margin"
android:paddingRight="@dimen/fab_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:gravity="center"
android:id="@+id/donation_text" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/lv_products"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@null"
android:scrollbars="none" />
</LinearLayout>
<!-- Main Loader -->
<RelativeLayout
android:id="@+id/loader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone">
<com.github.ybq.android.spinkit.SpinKitView xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:SpinKit_Color="?colorAccent" />
</RelativeLayout>
</RelativeLayout>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?><!--
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>.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/fab_margin"
android:paddingRight="@dimen/fab_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:gravity="center"
android:text="@string/donations_description" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/lv_purchases"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@null"
android:scrollbars="none" />
</LinearLayout>
<!-- Main Loader -->
<RelativeLayout
android:id="@+id/loader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone">
<com.github.ybq.android.spinkit.SpinKitView xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:SpinKit_Color="?colorAccent" />
</RelativeLayout>
</RelativeLayout>

View File

@ -3,7 +3,7 @@
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"
@ -16,7 +16,7 @@
<activity
android:name=".PeertubeActivity"
tools:node="mergeOnlyAttributes">
tools:node="merge">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -28,7 +28,25 @@
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" />
<activity
android:name=".DonationActivity"
android:configChanges="orientation|screenSize"
android:label="@string/support_the_app"
android:windowSoftInputMode="stateAlwaysHidden" />
</application>
</manifest>

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);
developer_mastodon.setText(content);
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);
});
@ -80,15 +80,6 @@ public class AboutActivity extends AppCompatActivity {
TextView app_name = findViewById(R.id.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
TextView framagit = findViewById(R.id.framagit);
@ -100,15 +91,6 @@ public class AboutActivity extends AppCompatActivity {
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);
if (BuildConfig.google_restriction || !BuildConfig.full_instances) {

View File

@ -19,6 +19,8 @@ import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Html;
import android.text.SpannableString;
import android.text.Spanned;
@ -52,6 +54,8 @@ import app.fedilab.fedilabtube.helper.SwitchAccountHelper;
import app.fedilab.fedilabtube.sqlite.AccountDAO;
import app.fedilab.fedilabtube.sqlite.Sqlite;
import static app.fedilab.fedilabtube.MainActivity.badgeCount;
public class AccountActivity extends AppCompatActivity {
@ -112,9 +116,13 @@ public class AccountActivity extends AppCompatActivity {
});
TabLayout.Tab notificationTab = binding.accountTabLayout.newTab();
if (Helper.isLoggedIn(AccountActivity.this)) {
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.title_notifications)));
if (badgeCount > 0) {
binding.accountTabLayout.addTab(notificationTab.setText(getString(R.string.title_notifications) + " (" + badgeCount + ")"));
} else {
binding.accountTabLayout.addTab(notificationTab.setText(getString(R.string.title_notifications)));
}
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.title_muted)));
binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.title_channel)));
@ -159,10 +167,30 @@ public class AccountActivity extends AppCompatActivity {
fragment = (Fragment) binding.accountViewpager.getAdapter().instantiateItem(binding.accountViewpager, tab.getPosition());
switch (tab.getPosition()) {
case 0:
if (badgeCount > 0) {
android.app.AlertDialog.Builder builder;
builder = new android.app.AlertDialog.Builder(AccountActivity.this);
builder.setMessage(R.string.mark_all_notifications_as_read_confirm);
builder.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(R.string.mark_all_as_read, (dialog, which) -> {
new Thread(() -> {
new RetrofitPeertubeAPI(AccountActivity.this).markAllAsRead();
Handler mainHandler = new Handler(Looper.getMainLooper());
badgeCount = 0;
Runnable myRunnable = () -> binding.accountTabLayout.getTabAt(0).setText(getString(R.string.title_notifications));
mainHandler.post(myRunnable);
}).start();
dialog.dismiss();
})
.setNegativeButton(R.string.no, (dialog, which) -> dialog.dismiss())
.show();
} else {
if (fragment != null) {
DisplayNotificationsFragment displayNotificationsFragment = ((DisplayNotificationsFragment) fragment);
displayNotificationsFragment.scrollToTop();
}
}
break;
case 1:
if (fragment != null) {
@ -195,6 +223,14 @@ public class AccountActivity extends AppCompatActivity {
}
}
public void updateCounter() {
if (badgeCount > 0) {
binding.accountTabLayout.getTabAt(0).setText(getString(R.string.title_notifications) + " (" + badgeCount + ")");
} else {
binding.accountTabLayout.getTabAt(0).setText(getString(R.string.title_notifications));
}
}
@Override
protected void onResume() {
super.onResume();
@ -210,6 +246,7 @@ public class AccountActivity extends AppCompatActivity {
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
overridePendingTransition(R.anim.slide_out_up, R.anim.slide_in_up_down);
return true;
} else if (item.getItemId() == R.id.action_add_account) {
SwitchAccountHelper.switchDialog(AccountActivity.this, true);
@ -255,5 +292,11 @@ public class AccountActivity extends AppCompatActivity {
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
overridePendingTransition(R.anim.slide_out_up, R.anim.slide_in_up_down);
}
}

View File

@ -65,7 +65,7 @@ public class BaseFedilabTube extends MultiDexApplication {
MultiDex.install(BaseFedilabTube.this);
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);

View File

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

View File

@ -16,33 +16,30 @@ package app.fedilab.fedilabtube;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Build;
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.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.appcompat.widget.TooltipCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
@ -51,18 +48,9 @@ import com.kobakei.ratethisapp.RateThisApp;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
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.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
@ -71,7 +59,7 @@ import java.util.regex.Pattern;
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
import app.fedilab.fedilabtube.client.data.AccountData.Account;
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.OauthParams;
import app.fedilab.fedilabtube.client.entities.PeertubeInformation;
@ -93,18 +81,13 @@ import app.fedilab.fedilabtube.sqlite.Sqlite;
import app.fedilab.fedilabtube.sqlite.StoredInstanceDAO;
import app.fedilab.fedilabtube.viewmodel.TimelineVM;
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.SURFING;
import static app.fedilab.fedilabtube.helper.Helper.isLoggedInType;
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;
@ -112,12 +95,12 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
public static UserMe userMe;
public static InstanceData.InstanceConfig instanceConfig;
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 DisplayOverviewFragment overviewFragment;
private ActivityMainBinding binding;
public static int badgeCount;
private final BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= item -> {
int itemId = item.getItemId();
@ -156,8 +139,7 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
}
return true;
};
private BroadcastReceiver manage_chromecast;
private VideoData.Video castedTube;
@SuppressLint("ApplySharedPref")
public static void showRadioButtonDialogFullInstances(Activity activity, boolean storeInDb) {
@ -232,129 +214,26 @@ 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
public void onDestroy() {
super.onDestroy();
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
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 = super.binding;
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
typeOfConnection = TypeOfConnection.UNKNOWN;
badgeCount = 0;
binding.navView.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
@ -392,6 +271,11 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
if (!Helper.isLoggedIn(MainActivity.this)) {
PagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
binding.viewpager.setAdapter(mPagerAdapter);
} else {
new Thread(() -> {
badgeCount = new RetrofitPeertubeAPI(MainActivity.this).unreadNotifications();
invalidateOptionsMenu();
}).start();
}
binding.viewpager.setOffscreenPageLimit(5);
@ -454,92 +338,11 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
PlaylistExportHelper.manageIntentUrl(MainActivity.this, getIntent());
}
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);
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) {
discoverCast();
}
@ -558,6 +361,12 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
}
}
@Override
public void onResume() {
super.onResume();
invalidateOptionsMenu();
}
private void refreshToken() {
new Thread(() -> {
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
@ -685,24 +494,31 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
MenuItem settingsItem = menu.findItem(R.id.action_settings);
MenuItem sepiaSearchItem = menu.findItem(R.id.action_sepia_search);
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 donateItem = menu.findItem(R.id.action_donate);
MenuItem changeInstanceItem = menu.findItem(R.id.action_change_instance);
FrameLayout rootView = (FrameLayout) accountItem.getActionView();
if (BuildConfig.surfing_mode && ((Helper.isLoggedIn(MainActivity.this) && typeOfConnection == NORMAL) || typeOfConnection == SURFING)) {
binding.instances.setVisibility(View.VISIBLE);
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);
});
FrameLayout redCircle = rootView.findViewById(R.id.view_alert_red_circle);
TextView countTextView = rootView.findViewById(R.id.view_alert_count_textview);
//change counter for notifications
if (badgeCount > 0) {
countTextView.setText(String.valueOf(badgeCount));
redCircle.setVisibility(View.VISIBLE);
} else {
binding.instances.setVisibility(View.GONE);
redCircle.setVisibility(View.GONE);
}
TooltipCompat.setTooltipText(accountItem.getActionView(), getText(R.string.account));
if (BuildConfig.FLAVOR.compareTo("google_full") == 0) {
donateItem.setVisible(true);
}
if (!BuildConfig.instance_switcher) {
changeInstanceItem.setVisible(false);
}
switch (typeOfConnection) {
case UNKNOWN:
instanceItem.setVisible(false);
accountItem.setVisible(false);
uploadItem.setVisible(false);
myVideosItem.setVisible(false);
@ -716,7 +532,9 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
case NORMAL:
accountItem.setVisible(true);
if (Helper.isLoggedIn(MainActivity.this)) {
instanceItem.setVisible(false);
if (!BuildConfig.full_instances) {
changeInstanceItem.setVisible(false);
}
uploadItem.setVisible(true);
myVideosItem.setVisible(true);
playslistItem.setVisible(true);
@ -728,7 +546,6 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
boolean checked = sharedpreferences.getBoolean(getString(R.string.set_store_in_history), true);
incognitoItem.setChecked(checked);
} else {
instanceItem.setVisible(true);
uploadItem.setVisible(false);
myVideosItem.setVisible(false);
playslistItem.setVisible(!BuildConfig.full_instances);
@ -739,7 +556,6 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
}
break;
case SURFING:
instanceItem.setVisible(false);
accountItem.setVisible(true);
uploadItem.setVisible(false);
myVideosItem.setVisible(false);
@ -751,9 +567,6 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
break;
}
if (!BuildConfig.instance_switcher) {
instanceItem.setVisible(false);
}
if (!BuildConfig.sepia_search) {
sepiaSearchItem.setVisible(false);
@ -782,13 +595,24 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
}).start();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
final MenuItem accountItem = menu.findItem(R.id.action_account);
FrameLayout rootView = (FrameLayout) accountItem.getActionView();
rootView.setOnClickListener(v -> onOptionsItemSelected(accountItem));
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
String type = null;
String action = "TIMELINE";
if (item.getItemId() == R.id.action_change_instance) {
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 {
showRadioButtonDialog();
}
@ -804,11 +628,14 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
} else {
if (Helper.canMakeAction(MainActivity.this)) {
intent = new Intent(MainActivity.this, AccountActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.slide_in_up, R.anim.slide_out_up);
} else {
intent = new Intent(MainActivity.this, LoginActivity.class);
}
startActivity(intent);
}
}
} else if (item.getItemId() == R.id.action_upload) {
Intent intent = new Intent(MainActivity.this, PeertubeUploadActivity.class);
startActivity(intent);
@ -847,6 +674,9 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
} else if (item.getItemId() == R.id.action_about) {
Intent intent = new Intent(MainActivity.this, AboutActivity.class);
startActivity(intent);
} else if (item.getItemId() == R.id.action_donate) {
Intent intent = new Intent(MainActivity.this, DonationActivity.class);
startActivity(intent);
} else if (item.getItemId() == R.id.action_incognito) {
item.setChecked(!item.isChecked());
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
@ -896,21 +726,19 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String acad = HelperInstance.getLiveInstance(MainActivity.this);
int i = 0;
HashMap<String, String> instancesMap = new HashMap<>(HelperAcadInstance.instances_themes);
Iterator<Map.Entry<String, String>> it = instancesMap.entrySet().iterator();
String[] academiesKey = new String[HelperAcadInstance.instances_themes.size()];
String[] academiesValue = new String[HelperAcadInstance.instances_themes.size()];
List<AcadInstances> acadInstances = AcadInstances.getInstances();
String[] academiesKey = new String[acadInstances.size()];
String[] academiesValue = new String[acadInstances.size()];
int position = 0;
while (it.hasNext()) {
Map.Entry<String, String> pair = it.next();
academiesKey[i] = pair.getKey();
academiesValue[i] = pair.getValue();
if (pair.getValue().compareTo(acad) == 0) {
for (AcadInstances ac : acadInstances) {
academiesKey[i] = ac.getName();
academiesValue[i] = ac.getUrl();
if (ac.getUrl().compareTo(acad) == 0) {
position = i;
}
it.remove();
i++;
}
alt_bld.setSingleChoiceItems(academiesKey, position, (dialog, item) -> {
String newInstance = academiesValue[item];
SharedPreferences.Editor editor = sharedpreferences.edit();

View File

@ -134,6 +134,9 @@ public class MastodonWebviewConnectActivity extends AppCompatActivity {
return false;
}
String code = val[1];
if (code.contains("&")) {
code = code.split("&")[0];
}
OauthParams oauthParams = new OauthParams();
oauthParams.setClient_id(clientId);
oauthParams.setClient_secret(clientSecret);

View File

@ -44,7 +44,6 @@ import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
@ -52,7 +51,6 @@ import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.view.inputmethod.InputMethodManager;
import android.webkit.MimeTypeMap;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
@ -63,14 +61,12 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.PopupMenu;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -111,8 +107,6 @@ import com.google.android.material.snackbar.Snackbar;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.text.DateFormat;
import java.util.ArrayList;
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.MastalabWebViewClient;
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.RATEVIDEO;
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_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.getAttColor;
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;
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 List<String> playedVideos = new ArrayList<>();
private String peertubeInstance, videoUuid;
private ImageView fullScreenIcon;
private SimpleExoPlayer player;
private boolean fullScreenMode;
private VideoData.Video peertube;
private int mode;
private Map<String, List<PlaylistExist>> playlists;
private boolean playInMinimized, autoPlay, autoFullscreen;
@ -218,8 +203,10 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
private String currentCaption;
private boolean isRemote;
private boolean willPlayFromIntent;
private String chromeCastVideoURL;
private app.fedilab.fedilabtube.client.mastodon.Status status;
Uri captionURI;
String captionLang;
public static void hideKeyboard(Activity activity) {
if (activity != null && activity.getWindow() != null) {
@ -305,10 +292,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityPeertubeBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
binding = super.binding;
videoOrientationType = videoOrientation.LANDSCAPE;
max_id = "0";
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
@ -510,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();
}
});
}
@ -664,7 +621,8 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
secInt = Integer.parseInt(sec.replace("s", ""));
totalSeconds += secInt;
}
captionURI = null;
captionLang = null;
if (instance != null && uuid != null) {
peertubeInstance = instance.replace("https://", "").replace("http://", "");
sepiaSearch = true; // Sepia search flag is used because, at this time we don't know if the video is federated.
@ -795,20 +753,6 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
}).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
public boolean onOptionsItemSelected(MenuItem item) {
@ -818,101 +762,6 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
}
finish();
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);
}
@ -1018,7 +867,8 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
secInt = Integer.parseInt(sec.replace("strue", ""));
totalSeconds += secInt;
}
captionURI = null;
captionLang = null;
if (instance != null && uuid != null) {
peertubeInstance = instance.replace("https://", "").replace("http://", "");
sepiaSearch = true; // Sepia search flag is used because, at this time we don't know if the video is federated.
@ -1069,6 +919,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
PlayerControlView controlView = binding.doubleTapPlayerView.findViewById(R.id.exo_controller);
DefaultTimeBar exo_progress = controlView.findViewById(R.id.exo_progress);
TextView exo_duration = controlView.findViewById(R.id.exo_duration);
TextView exo_position = controlView.findViewById(R.id.exo_position);
TextView exo_live_badge = controlView.findViewById(R.id.exo_live_badge);
if (peertube.isLive()) {
exo_progress.setVisibility(View.INVISIBLE);
@ -1076,9 +927,11 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
exo_live_badge.setVisibility(View.VISIBLE);
exo_live_badge.setText(R.string.live);
exo_live_badge.setBackgroundResource(R.drawable.rounded_live);
exo_position.setVisibility(View.GONE);
} else {
exo_progress.setVisibility(View.VISIBLE);
exo_live_badge.setVisibility(View.GONE);
exo_position.setVisibility(View.VISIBLE);
exo_duration.setBackground(null);
}
@ -1137,6 +990,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
if (peertube.isLive()) {
info_duration.setText(R.string.live);
info_duration.setBackgroundResource(R.drawable.rounded_live);
info_duration.setBackgroundResource(R.drawable.rounded_live);
} else {
info_duration.setText(Helper.secondsToString(peertube.getDuration()));
}
@ -1409,6 +1263,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
});
}
/**
* Manage video to play with different factors
*
@ -1421,7 +1276,10 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
* @param lang String ("en","fr", etc.)
*/
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);
int video_cache = sharedpreferences.getInt(Helper.SET_VIDEO_CACHE, Helper.DEFAULT_VIDEO_CACHE_MB);
if (videoURL != null && (videoURL.endsWith(".torrent") || videoURL.startsWith("magnet"))) {
@ -1442,7 +1300,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
e.printStackTrace();
}
}
if (video_cache == 0 || dataSourceFactory != null) {
if (video_cache == 0 || dataSourceFactory != null || video.isLive()) {
if (dataSourceFactory == null) {
dataSourceFactory = new DefaultDataSourceFactory(PeertubeActivity.this,
Util.getUserAgent(PeertubeActivity.this, null), null);
@ -1462,8 +1320,6 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
} else {
CacheDataSourceFactory cacheDataSourceFactory = new CacheDataSourceFactory(PeertubeActivity.this);
MediaItem mediaItem = new MediaItem.Builder().setUri(videoURL).build();
videoSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(mediaItem);
if (subtitles != null) {
MediaItem.Subtitle mediaSubtitle = new MediaItem.Subtitle(subtitles, MimeTypes.TEXT_VTT, lang, Format.NO_VALUE);
subtitleSource = new SingleSampleMediaSource.Factory(cacheDataSourceFactory).createMediaSource(mediaSubtitle, C.TIME_UNSET);
@ -1497,6 +1353,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
if (autoPlay) {
binding.doubleTapPlayerView.hideController();
}
// loadCast(video, videoURL, subtitles!=null?subtitles.toString():null);
}
private void fetchComments() {
@ -1531,14 +1388,14 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
} else {
videoURL = peertube.getFileUrl(resolution, PeertubeActivity.this);
}
if (peertube != null && peertube.isWaitTranscoding() && peertube.isLive()) {
if (peertube != null && peertube.isLive() && videoURL == null) {
View parentLayout = findViewById(android.R.id.content);
Snackbar snackbar = Snackbar.make(parentLayout, R.string.live_not_started, Snackbar.LENGTH_INDEFINITE);
snackbar.setAction(R.string.close, view -> finish());
snackbar.show();
return;
}
chromeCastVideoURL = videoURL;
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
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)) {
@ -1568,7 +1425,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
@Override
public void onConfigurationChanged(@NotNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (binding.castController.getVisibility() == View.VISIBLE) {
if (binding.minController.castMiniController.getVisibility() == View.VISIBLE) {
return;
}
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
@ -1621,7 +1478,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
public void onResume() {
super.onResume();
onStopCalled = false;
if (player != null && !player.isPlaying()) {
if (player != null && !player.isPlaying() && binding.minController.castMiniController.getVisibility() != View.VISIBLE) {
player.setPlayWhenReady(autoPlay);
if (autoPlay) {
binding.doubleTapPlayerView.hideController();
@ -1637,7 +1494,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
}
if (player != null && (!isPlayInMinimized || !playInMinimized)) {
player.setPlayWhenReady(false);
} else if (playInMinimized && binding.castController.getVisibility() != View.VISIBLE) {
} else if (playInMinimized && binding.minController.castMiniController.getVisibility() != View.VISIBLE) {
enterVideoMode();
}
}
@ -1652,7 +1509,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
public void onReceive(Context context, Intent intent) {
String strAction = intent.getAction();
if (strAction.equals(Intent.ACTION_SCREEN_OFF)) {
if (player != null && isPlayInMinimized) {
if (player != null) {
if (!sharedpreferences.getBoolean(getString(R.string.set_play_screen_lock_choice), false)) {
player.setPlayWhenReady(false);
} else {
@ -1939,11 +1796,12 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
binding.mediaVideo.player(player);
binding.doubleTapPlayerView.setPlayer(player);
binding.loader.setVisibility(View.GONE);
startStream(
peertube,
null,
res,
true, position, null, null, false);
true, position, captionURI, captionLang, false);
}
break;
case SPEED:
@ -1955,7 +1813,6 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
}
break;
case CAPTION:
Uri uri = null;
Caption captionToUse = null;
for (Caption caption : captions) {
if (caption.getLanguage().getId().compareTo(item.getStrId()) == 0) {
@ -1965,10 +1822,12 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
}
if (captionToUse != null) {
if (!sepiaSearch) {
uri = Uri.parse("https://" + HelperInstance.getLiveInstance(PeertubeActivity.this) + captionToUse.getCaptionPath());
captionURI = Uri.parse("https://" + HelperInstance.getLiveInstance(PeertubeActivity.this) + captionToUse.getCaptionPath());
} else {
uri = Uri.parse("https://" + peertubeInstance + captionToUse.getCaptionPath());
captionURI = Uri.parse("https://" + peertubeInstance + captionToUse.getCaptionPath());
}
} else {
captionURI = null;
}
currentCaption = item.getStrId();
long newPosition = player.getCurrentPosition();
@ -1980,14 +1839,15 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
player = new SimpleExoPlayer.Builder(PeertubeActivity.this).setTrackSelector(trackSelector).build();
binding.mediaVideo.player(player);
binding.doubleTapPlayerView.setPlayer(player);
captionLang = item.getStrId();
startStream(
peertube,
null,
null,
true,
newPosition,
uri,
item.getStrId(),
captionURI,
captionLang,
false
);
break;
@ -2360,16 +2220,16 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd
DrawableCompat.setTint(bookmark, color);
}
if (status.isReblogged()) {
if (reblog != null && status.isReblogged()) {
reblog.setColorFilter(getResources().getColor(R.color.positive_thumbs), PorterDuff.Mode.SRC_ATOP);
DrawableCompat.setTint(reblog, getResources().getColor(R.color.positive_thumbs));
}
if (status.isFavourited()) {
if (favorite != null && status.isFavourited()) {
favorite.setColorFilter(getResources().getColor(R.color.favorite), PorterDuff.Mode.SRC_ATOP);
DrawableCompat.setTint(favorite, getResources().getColor(R.color.favorite));
}
if (status.isBookmarked()) {
if (bookmark != null && status.isBookmarked()) {
bookmark.setColorFilter(getResources().getColor(R.color.bookmark), PorterDuff.Mode.SRC_ATOP);
DrawableCompat.setTint(bookmark, getResources().getColor(R.color.bookmark));
}

View File

@ -284,6 +284,16 @@ public interface PeertubeService {
@GET("users/me/notifications")
Call<NotificationData> getNotifications(@Header("Authorization") String credentials, @Query("start") String maxId, @Query("count") String count, @Query("since_id") String sinceId);
@GET("users/me/notifications?start=0&count=0&unread=true")
Call<NotificationData> countNotifications(@Header("Authorization") String credentials);
@POST("users/me/notifications/read-all")
Call<String> markAllAsRead(@Header("Authorization") String credentials);
@FormUrlEncoded
@POST("users/me/notifications/read")
Call<String> markAsRead(@Header("Authorization") String credentials, @Field("ids[]") List<String> ids);
//Update Notification settings
@PUT("users/me/notification-settings")
Call<String> updateNotifications(@Header("Authorization") String credentials, @Body NotificationSettings notificationSettings);

View File

@ -43,6 +43,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import app.fedilab.fedilabtube.BuildConfig;
import app.fedilab.fedilabtube.MainActivity;
@ -85,6 +86,7 @@ import app.fedilab.fedilabtube.viewmodel.PlaylistsVM;
import app.fedilab.fedilabtube.viewmodel.TimelineVM;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
@ -103,6 +105,13 @@ public class RetrofitPeertubeAPI {
private String token;
private Set<String> selection;
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.build();
public RetrofitPeertubeAPI(Context context) {
_context = context;
instance = HelperInstance.getLiveInstance(context);
@ -199,6 +208,7 @@ public class RetrofitPeertubeAPI {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(finalUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
if (token == null) {
@ -215,6 +225,7 @@ public class RetrofitPeertubeAPI {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
return retrofit.create(PeertubeService.class);
}
@ -283,6 +294,50 @@ public class RetrofitPeertubeAPI {
}
/**
* Retrieve notifications
*
* @return APIResponse
*/
public int unreadNotifications() {
PeertubeService peertubeService = init();
Call<NotificationData> notificationsCall = peertubeService.countNotifications("Bearer " + token);
try {
Response<NotificationData> response = notificationsCall.execute();
if (response.isSuccessful() && response.body() != null) {
return response.body().total;
}
} catch (IOException ignored) {
}
return 0;
}
/**
* Mark all notifications as read
*/
public void markAllAsRead() {
PeertubeService peertubeService = init();
Call<String> notificationsCall = peertubeService.markAllAsRead("Bearer " + token);
try {
Response<String> response = notificationsCall.execute();
} catch (IOException ignored) {
}
}
/**
* Mark a notification as read
*/
public void markAsRead(String id) {
PeertubeService peertubeService = init();
ArrayList<String> ids = new ArrayList<>();
ids.add(id);
Call<String> notificationsCall = peertubeService.markAsRead("Bearer " + token, ids);
try {
Response<String> response = notificationsCall.execute();
} catch (IOException ignored) {
}
}
/**
* Retrieve notifications
*
@ -1525,7 +1580,10 @@ public class RetrofitPeertubeAPI {
}
try {
RequestBody displayName = RequestBody.create(playlistParams.getDisplayName(), MediaType.parse("text/plain"));
RequestBody description = RequestBody.create(playlistParams.getDescription(), MediaType.parse("text/plain"));
RequestBody description = null;
if (playlistParams.getDescription() != null) {
description = RequestBody.create(playlistParams.getDescription(), MediaType.parse("text/plain"));
}
RequestBody channelId = RequestBody.create(playlistParams.getVideoChannelId(), MediaType.parse("text/plain"));
if (apiAction == PlaylistsVM.action.CREATE_PLAYLIST) {
Call<VideoPlaylistData.VideoPlaylistCreation> stringCall = peertubeService.addPlaylist(getToken(), displayName, description, playlistParams.getPrivacy(), channelId, bodyThumbnail);

View File

@ -205,14 +205,19 @@ public class VideoData {
}
public List<File> getAllFile(Context context) {
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
int mode = sharedpreferences.getInt(Helper.SET_VIDEO_MODE, Helper.VIDEO_MODE_NORMAL);
if (files != null && files.size() > 0) {
return files;
} else if (streamingPlaylists != null) {
List<File> files = new ArrayList<>();
for (StreamingPlaylists streamingPlaylists : streamingPlaylists) {
if (streamingPlaylists.getFiles().size() > 0) {
files.addAll(streamingPlaylists.getFiles());
} else {
File file = new File();
file.setFileUrl(streamingPlaylists.getPlaylistUrl());
file.setFileDownloadUrl(streamingPlaylists.getPlaylistUrl());
files.add(file);
}
}
return files;
}
@ -225,24 +230,40 @@ public class VideoData {
for (File file : files) {
if (file.getResolutions().getLabel().compareTo(resolution) == 0) {
if (mode == Helper.VIDEO_MODE_MAGNET) {
if (file.getMagnetUri() != null) {
return file.getMagnetUri();
} else {
return file.getFileUrl();
}
} else if (mode == Helper.VIDEO_MODE_TORRENT) {
if (file.getTorrentUrl() != null) {
return file.getTorrentUrl();
} else {
return file.getFileUrl();
}
} else {
return file.getFileUrl();
}
}
}
}
File file = Helper.defaultFile(context, files);
if (file != null) {
if (mode == Helper.VIDEO_MODE_MAGNET) {
if (file.getMagnetUri() != null) {
return file.getMagnetUri();
} else {
return file.getFileUrl();
}
} else if (mode == Helper.VIDEO_MODE_TORRENT) {
if (file.getTorrentUrl() != null) {
return file.getTorrentUrl();
} else {
return file.getFileUrl();
}
} else {
return file.getFileUrl();
}
} else {
return null;
}

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

@ -28,15 +28,16 @@ import retrofit2.http.Query;
interface MastodonService {
@FormUrlEncoded
@POST("apps")
Call<Oauth> getOauth(
@Query("client_name") String client_name,
@Query("redirect_uris") String redirect_uris,
@Query("scopes") String scopes,
@Query("website") String website);
@Field("client_name") String client_name,
@Field("redirect_uris") String redirect_uris,
@Field("scopes") String scopes,
@Field("website") String website);
@FormUrlEncoded
@POST("/oauth/token")
@POST("oauth/token")
Call<Token> createToken(
@Field("grant_type") String grant_type,
@Field("client_id") String client_id,
@ -53,12 +54,13 @@ interface MastodonService {
@Query("q") String messageURL
);
@FormUrlEncoded
@POST("statuses")
Call<Status> postReply(
@Header("Authorization") String credentials,
@Query("in_reply_to_id") String inReplyToId,
@Query("status") String content,
@Query("visibility") String visibility
@Field("in_reply_to_id") String inReplyToId,
@Field("status") String content,
@Field("visibility") String visibility
);

View File

@ -25,6 +25,7 @@ import android.os.Looper;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.concurrent.TimeUnit;
import app.fedilab.fedilabtube.MainActivity;
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.sqlite.MastodonAccountDAO;
import app.fedilab.fedilabtube.sqlite.Sqlite;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
@ -49,6 +51,12 @@ public class RetrofitMastodonAPI {
private String instance;
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 {
MastodonService mastodonService2 = init2();
Call<Results> statusCall = mastodonService2.searchMessage(getToken(), url);
@ -142,10 +150,25 @@ public class RetrofitMastodonAPI {
}).start();
}
private MastodonService init_no_api() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
if (token == null) {
token = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null);
}
return retrofit.create(MastodonService.class);
}
private MastodonService init() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(finalUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
if (token == null) {
@ -158,6 +181,7 @@ public class RetrofitMastodonAPI {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(finalUrl2)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
SharedPreferences sharedpreferences = _context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
if (token == null) {
@ -223,7 +247,7 @@ public class RetrofitMastodonAPI {
* @return Account
*/
public Token manageToken(OauthParams oauthParams) throws Error {
MastodonService mastodonService = init();
MastodonService mastodonService = init_no_api();
Call<Token> createToken = mastodonService.createToken(
oauthParams.getGrant_type(),
oauthParams.getClient_id(),
@ -274,7 +298,7 @@ public class RetrofitMastodonAPI {
return null;
}
public Status postAction(actionType type, Status status) throws Error {
public Status postAction(actionType type, Status status) {
MastodonService mastodonService = init();
Call<Status> postAction = null;
if (status != null) {

View File

@ -31,10 +31,12 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import app.fedilab.fedilabtube.AccountActivity;
import app.fedilab.fedilabtube.PeertubeActivity;
import app.fedilab.fedilabtube.R;
import app.fedilab.fedilabtube.ShowAccountActivity;
import app.fedilab.fedilabtube.ShowChannelActivity;
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
import app.fedilab.fedilabtube.client.data.AccountData;
import app.fedilab.fedilabtube.client.data.ChannelData;
import app.fedilab.fedilabtube.client.data.NotificationData.Notification;
@ -43,6 +45,8 @@ import app.fedilab.fedilabtube.fragment.DisplayNotificationsFragment;
import app.fedilab.fedilabtube.helper.Helper;
import app.fedilab.fedilabtube.helper.HelperInstance;
import static app.fedilab.fedilabtube.MainActivity.badgeCount;
public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@ -72,6 +76,11 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
holder.peertube_notif_pp.setVisibility(View.VISIBLE);
AccountData.Account accountAction = null;
ChannelData.Channel channelAction = null;
if (notification.isRead()) {
holder.unread.setVisibility(View.INVISIBLE);
} else {
holder.unread.setVisibility(View.VISIBLE);
}
if (notification.getActorFollow() != null) {
String profileUrl = notification.getActorFollow().getFollower().getAvatar() != null ? notification.getActorFollow().getFollower().getAvatar().getPath() : null;
Helper.loadGiF(context, profileUrl, holder.peertube_notif_pp);
@ -93,6 +102,7 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
accountAction.setDisplayName(actor.getDisplayName());
accountAction.setHost(actor.getHost());
accountAction.setUsername(actor.getName());
holder.peertube_notif_message.setOnClickListener(v -> markAsRead(notification, position));
} else if (notification.getComment() != null) { //Comment Notification
String profileUrl = notification.getComment().getAccount().getAvatar() != null ? notification.getComment().getAccount().getAvatar().getPath() : null;
Helper.loadGiF(context, profileUrl, holder.peertube_notif_pp);
@ -112,6 +122,7 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
b.putString("video_id", notification.getComment().getVideo().getId());
b.putString("video_uuid", notification.getComment().getVideo().getUuid());
intent.putExtras(b);
markAsRead(notification, position);
context.startActivity(intent);
});
} else {
@ -155,6 +166,7 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
b.putString("video_uuid", notification.getVideo().getUuid());
intent.putExtras(b);
context.startActivity(intent);
markAsRead(notification, position);
});
} else if (notification.getVideoAbuse() != null && notification.getVideoAbuse().getVideo() != null) {
message = context.getString(R.string.peertube_video_abuse, notification.getVideoAbuse().getVideo().getName());
@ -163,6 +175,7 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
holder.peertube_notif_message.setText(Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY));
else
holder.peertube_notif_message.setText(Html.fromHtml(message));
holder.peertube_notif_message.setOnClickListener(v -> markAsRead(notification, position));
} else if (notification.getAbuse() != null) {
clickableNotification = false;
if (notification.getType() == DisplayNotificationsFragment.MY_VIDEO_REPPORT_SUCCESS) {
@ -172,6 +185,7 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
holder.peertube_notif_message.setText(Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY));
else
holder.peertube_notif_message.setText(Html.fromHtml(message));
holder.peertube_notif_message.setOnClickListener(v -> markAsRead(notification, position));
}
}
holder.peertube_notif_date.setText(Helper.dateDiff(context, notification.getCreatedAt()));
@ -197,6 +211,17 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
}
}
private void markAsRead(Notification notification, int position) {
if (!notification.isRead()) {
notification.setRead(true);
badgeCount--;
if (context instanceof AccountActivity) {
((AccountActivity) context).updateCounter();
}
notifyItemChanged(position);
new Thread(() -> new RetrofitPeertubeAPI(context).markAsRead(notification.getId())).start();
}
}
@Override
public long getItemId(int position) {
@ -212,7 +237,7 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView peertube_notif_pp;
TextView peertube_notif_message, peertube_notif_date;
TextView peertube_notif_message, peertube_notif_date, unread;
RelativeLayout main_container_trans;
public ViewHolder(View itemView) {
@ -221,6 +246,8 @@ public class PeertubeNotificationsListAdapter extends RecyclerView.Adapter<Recyc
peertube_notif_message = itemView.findViewById(R.id.peertube_notif_message);
peertube_notif_date = itemView.findViewById(R.id.peertube_notif_date);
main_container_trans = itemView.findViewById(R.id.container_trans);
unread = itemView.findViewById(R.id.unread);
}
public View getView() {

View File

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

View File

@ -34,6 +34,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import app.fedilab.fedilabtube.BuildConfig;
import app.fedilab.fedilabtube.MainActivity;
import app.fedilab.fedilabtube.R;
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[] entryValuesTheme = new CharSequence[3];
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[1] = String.valueOf(Helper.DARK_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);
//****** 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));
assert set_cast_choice != null;
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 APP_PREFS = "app_prefs";
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 String RECEIVE_ACTION = "receive_action";
public static final String SET_UNFOLLOW_VALIDATION = "set_unfollow_validation";
@ -691,4 +692,6 @@ public class Helper {
return String.format(Locale.getDefault(), "%s%s", df.format(rounded), context.getString(R.string.b));
}
}
}

View File

@ -32,6 +32,64 @@ public class HelperAcadInstance {
public static String SUBSCRIPTIONS = "ABONNEMENTS";
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
public static String[] valideEmails = {
"ac-aix-marseille.fr",
@ -71,10 +129,11 @@ public class HelperAcadInstance {
"igesr.gouv.fr"
};
static {
Map<String, String> is = new LinkedHashMap<>();
is.put("Normandie", "tube-normandie.beta.education.fr");
is.put("Enseignement professionnel", "tube-enseignement-professionnel.apps.education.fr");
//TODO: remove comments when instances will be available
/* is.put("Enseignement professionnel", "tube-enseignement-professionnel.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("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("Maternelle", "tube-maternelle.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);
}

View File

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

View File

@ -244,7 +244,7 @@ public class AccountDAO {
public List<Account> getAllPeertubeAccount() {
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);
} catch (Exception e) {
e.printStackTrace();

View File

@ -107,7 +107,7 @@ public class MastodonPostActionsVM extends AndroidViewModel {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> statusMutableLiveData.setValue(statusReply);
mainHandler.post(myRunnable);
} catch (Exception | Error e) {
} catch (Exception e) {
e.printStackTrace();
}
}).start();

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/holo_red_dark" />
<size
android:width="10dp"
android:height="10dp" />
</shape>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="@color/colorAccent"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20,4H4C2.89,4 2.01,4.89 2.01,6L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V6C22,4.89 21.11,4 20,4zM8.5,15H7.3l-2.55,-3.5V15H3.5V9h1.25l2.5,3.5V9H8.5V15zM13.5,10.26H11v1.12h2.5v1.26H11v1.11h2.5V15h-4V9h4V10.26zM20.5,14c0,0.55 -0.45,1 -1,1h-4c-0.55,0 -1,-0.45 -1,-1V9h1.25v4.51h1.13V9.99h1.25v3.51h1.12V9h1.25V14z" />
</vector>

View File

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

View File

@ -46,7 +46,7 @@
android:id="@+id/instance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginTop="5dp"
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="18sp"

View File

@ -81,6 +81,34 @@
</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
android:id="@+id/login_uid_container"
android:layout_width="0dp"
@ -88,7 +116,7 @@
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="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
android:id="@+id/login_uid"
@ -121,27 +149,7 @@
</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
android:id="@+id/barrier"

View File

@ -44,16 +44,6 @@
app:layout_scrollFlags="scroll|enterAlways"
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
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -75,58 +75,9 @@
android:scaleType="fitCenter"
android:visibility="gone" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cast_controller"
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>
<include
android:id="@+id/min_controller"
layout="@layout/min_controller" />
<app.fedilab.fedilabtube.webview.CustomWebview
android:id="@+id/webview_video"
@ -689,6 +640,5 @@
android:layout_gravity="center"
android:layout_margin="30dp" />
</RelativeLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:contentDescription="@string/account"
android:src="@drawable/ic_outline_account_circle_24" />
<FrameLayout
android:id="@+id/view_alert_red_circle"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_gravity="top|end"
android:background="@drawable/circle_red"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/view_alert_count_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="@color/white"
android:textSize="10sp"
tools:text="3" />
</FrameLayout>
</FrameLayout>

View File

@ -15,6 +15,7 @@
see <http://www.gnu.org/licenses>.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -38,13 +39,27 @@
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/unread"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="invisible"
app:drawableStartCompat="@drawable/ic_baseline_fiber_new_24" />
<TextView
android:id="@+id/peertube_notif_date"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="end"
android:textAlignment="viewEnd" />
</LinearLayout>
<TextView
android:id="@+id/peertube_notif_message"

View File

@ -9,22 +9,21 @@
android:title="@string/search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always|collapseActionView" />
<item
android:id="@+id/action_account"
app:actionLayout="@layout/counter_account_icon"
android:title="@string/account"
app:showAsAction="ifRoom" />
<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
android:id="@+id/action_playlist"
android:icon="@drawable/ic_baseline_playlist_play_24"
android:title="@string/playlists"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_account"
android:icon="@drawable/ic_outline_account_circle_24"
android:title="@string/account"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_upload"
android:icon="@drawable/ic_baseline_cloud_upload_24"
@ -67,4 +66,10 @@
android:icon="@drawable/ic_baseline_info_24"
android:title="@string/about_the_app"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_donate"
android:icon="@drawable/ic_baseline_attach_money_24"
android:title="@string/make_a_donation"
android:visible="false"
app:showAsAction="ifRoom" />
</menu>

View File

@ -361,4 +361,22 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -357,4 +357,22 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -356,4 +356,22 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -357,4 +357,20 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</string>
<string name="cancel_subscription">Cancel subscription</string>
<string name="cancel_subscription_confirm">Are you sure, you want to cancel that subscription?</string>
</resources>

View File

@ -357,4 +357,22 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -357,4 +357,22 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -356,4 +356,22 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -356,4 +356,22 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -357,4 +357,22 @@
<string name="watermark">Watermerk</string>
<string name="toast_code_error">Er is een fout opgetreden! De instantie gaf geen autorisatiecode terug!</string>
<string name="remote_account_from"><b>%1$s</b> extern account verbonden met de app.\n\nU kunt doorgaan naar bepaalde acties.</string>
<string name="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -359,4 +359,22 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -357,4 +357,22 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -358,4 +358,22 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -133,9 +133,9 @@
<string name="action_follow">Подписка</string>
<string name="action_mute">Игнорировать</string>
<string name="unlimited">Без ограничений</string>
<string name="peers">%1$d Peers</string>
<string name="b">B</string>
<string name="kb">KB</string>
<string name="peers">%1$d пиров</string>
<string name="b">Б</string>
<string name="kb">КБ</string>
<string name="mb">МБ</string>
<string name="gb">ГБ</string>
<string name="total_video_quota">Общая квота видео</string>
@ -357,6 +357,24 @@
<string name="instance_not_availabe">Экземпляр недоступен!</string>
<string name="max_tag_size">На видео не должно быть более 5 тегов!</string>
<string name="watermark">Водяной знак</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="toast_code_error">Произошла ошибка! Экземпляр не вернул код авторизации!</string>
<string name="remote_account_from"><b>%1$s</b> удаленная учетная запись, связанная с приложением.\n\nВы можете перейти к некоторым ограниченным действиям.</string>
<string name="donate">Пожертвование</string>
<string name="my_donations">Мои пожертвования</string>
<string name="one_time_donation_text">Здесь вы можете сделать единовременное пожертвование для поддержки разработки приложения. Это действие не принесет дополнительных возможностей!</string>
<string name="recurrent_donation_text">Здесь вы можете сделать единовременное пожертвование для поддержки разработки приложения. Это действие не принесет дополнительных возможностей!</string>
<string name="make_a_donation">Сделать пожертвование</string>
<string name="donations_description">Здесь вы найдете список ваших периодических пожертвований, сделанных для поддержки разработки приложения! Спасибо!</string>
<string name="support_the_app">Поддержка приложения</string>
<string name="donation_cancelled">Пожертвование было отменено!</string>
<string name="donation_succeeded_null">Благодарим Вас за ваше пожертвование!</string>
<string name="donation_succeeded">Спасибо за пожертвование в %1$s!</string>
<string name="one_time">Один раз</string>
<string name="my_subscriptions">Моя подписка</string>
<string name="month">Месяц</string>
<string name="subscription_cancelled">Подписка отменена!</string>
<string name="cancel_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>

View File

@ -357,4 +357,22 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -355,4 +355,22 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -356,4 +356,22 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

View File

@ -455,5 +455,23 @@
<string name="watermark">Watermark</string>
<string name="toast_code_error">An error occurred! The instance did not return an authorisation code!</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="donate">Donate</string>
<string name="my_donations">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="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="make_a_donation">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="support_the_app">Support the app</string>
<string name="donation_cancelled">Donation has been cancelled!</string>
<string name="donation_succeeded_null">Thank you for your donation!</string>
<string name="donation_succeeded">Thank you for your donation of %1$s!</string>
<string name="one_time">One time</string>
<string name="my_subscriptions">My subscription</string>
<string name="month">Month</string>
<string name="subscription_cancelled">Subscription cancelled!</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="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>

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

@ -0,0 +1,20 @@
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>. */
//Do nothing
public class DonationActivity {
}

View File

@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
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"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
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();
}
}
}

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