mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-07 21:04:34 -05:00
Merge branch 'master' of github.com:Suwayomi/Tachidesk-Server
This commit is contained in:
@@ -35,6 +35,7 @@ open class ConfigManager {
|
|||||||
/**
|
/**
|
||||||
* Get a config module (Java API)
|
* Get a config module (Java API)
|
||||||
*/
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : ConfigModule> module(type: Class<T>): T = loadedModules[type] as T
|
fun <T : ConfigModule> module(type: Class<T>): T = loadedModules[type] as T
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
package com.f2prateek;
|
|
||||||
//TODO Consider if we can change this package into an Android dependency
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 Prateek Srivastava
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
This file may have been modified after being copied from it's original source.
|
|
||||||
*/
|
|
||||||
package com.f2prateek.rx.preferences;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
final class BooleanAdapter implements Preference.Adapter<Boolean> {
|
|
||||||
static final BooleanAdapter INSTANCE = new BooleanAdapter();
|
|
||||||
|
|
||||||
@Override public Boolean get(@NonNull String key, @NonNull SharedPreferences preferences) {
|
|
||||||
return preferences.getBoolean(key, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void set(@NonNull String key, @NonNull Boolean value,
|
|
||||||
@NonNull SharedPreferences.Editor editor) {
|
|
||||||
editor.putBoolean(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 Prateek Srivastava
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
This file may have been modified after being copied from it's original source.
|
|
||||||
*/
|
|
||||||
package com.f2prateek.rx.preferences;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
final class EnumAdapter<T extends Enum<T>> implements Preference.Adapter<T> {
|
|
||||||
private final Class<T> enumClass;
|
|
||||||
|
|
||||||
EnumAdapter(Class<T> enumClass) {
|
|
||||||
this.enumClass = enumClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public T get(@NonNull String key, @NonNull SharedPreferences preferences) {
|
|
||||||
String value = preferences.getString(key, null);
|
|
||||||
assert value != null; // Not called unless key is present.
|
|
||||||
return Enum.valueOf(enumClass, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void set(@NonNull String key, @NonNull T value, @NonNull SharedPreferences.Editor editor) {
|
|
||||||
editor.putString(key, value.name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 Prateek Srivastava
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
This file may have been modified after being copied from it's original source.
|
|
||||||
*/
|
|
||||||
package com.f2prateek.rx.preferences;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
final class FloatAdapter implements Preference.Adapter<Float> {
|
|
||||||
static final FloatAdapter INSTANCE = new FloatAdapter();
|
|
||||||
|
|
||||||
@Override public Float get(@NonNull String key, @NonNull SharedPreferences preferences) {
|
|
||||||
return preferences.getFloat(key, 0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void set(@NonNull String key, @NonNull Float value,
|
|
||||||
@NonNull SharedPreferences.Editor editor) {
|
|
||||||
editor.putFloat(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 Prateek Srivastava
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
This file may have been modified after being copied from it's original source.
|
|
||||||
*/
|
|
||||||
package com.f2prateek.rx.preferences;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
final class IntegerAdapter implements Preference.Adapter<Integer> {
|
|
||||||
static final IntegerAdapter INSTANCE = new IntegerAdapter();
|
|
||||||
|
|
||||||
@Override public Integer get(@NonNull String key, @NonNull SharedPreferences preferences) {
|
|
||||||
return preferences.getInt(key, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void set(@NonNull String key, @NonNull Integer value,
|
|
||||||
@NonNull SharedPreferences.Editor editor) {
|
|
||||||
editor.putInt(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 Prateek Srivastava
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
This file may have been modified after being copied from it's original source.
|
|
||||||
*/
|
|
||||||
package com.f2prateek.rx.preferences;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
final class LongAdapter implements Preference.Adapter<Long> {
|
|
||||||
static final LongAdapter INSTANCE = new LongAdapter();
|
|
||||||
|
|
||||||
@Override public Long get(@NonNull String key, @NonNull SharedPreferences preferences) {
|
|
||||||
return preferences.getLong(key, 0L);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void set(@NonNull String key, @NonNull Long value,
|
|
||||||
@NonNull SharedPreferences.Editor editor) {
|
|
||||||
editor.putLong(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 Prateek Srivastava
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
This file has been modified after being copied from it's original source.
|
|
||||||
*/
|
|
||||||
package com.f2prateek.rx.preferences;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.support.annotation.CheckResult;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
|
|
||||||
/** A preference of type {@link T}. Instances can be created from {@link RxSharedPreferences}. */
|
|
||||||
public final class Preference<T> {
|
|
||||||
/** Stores and retrieves instances of {@code T} in {@link SharedPreferences}. */
|
|
||||||
public interface Adapter<T> {
|
|
||||||
/** Retrieve the value for {@code key} from {@code preferences}. */
|
|
||||||
T get(@NonNull String key, @NonNull SharedPreferences preferences);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store non-null {@code value} for {@code key} in {@code editor}.
|
|
||||||
* <p>
|
|
||||||
* Note: Implementations <b>must not</b> call {@code commit()} or {@code apply()} on
|
|
||||||
* {@code editor}.
|
|
||||||
*/
|
|
||||||
void set(@NonNull String key, @NonNull T value, @NonNull SharedPreferences.Editor editor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final SharedPreferences preferences;
|
|
||||||
private final String key;
|
|
||||||
private final T defaultValue;
|
|
||||||
private final Adapter<T> adapter;
|
|
||||||
private final Observable<T> values;
|
|
||||||
|
|
||||||
Preference(SharedPreferences preferences, final String key, T defaultValue, Adapter<T> adapter,
|
|
||||||
Observable<String> keyChanges) {
|
|
||||||
this.preferences = preferences;
|
|
||||||
this.key = key;
|
|
||||||
this.defaultValue = defaultValue;
|
|
||||||
this.adapter = adapter;
|
|
||||||
this.values = keyChanges
|
|
||||||
.filter(key::equals)
|
|
||||||
.startWith("<init>") // Dummy value to trigger initial load.
|
|
||||||
.onBackpressureLatest()
|
|
||||||
.map(ignored -> get());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The key for which this preference will store and retrieve values. */
|
|
||||||
@NonNull
|
|
||||||
public String key() {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The value used if none is stored. May be {@code null}. */
|
|
||||||
@Nullable
|
|
||||||
public T defaultValue() {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the current value for this preference. Returns {@link #defaultValue()} if no value is
|
|
||||||
* set.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public T get() {
|
|
||||||
if (!preferences.contains(key)) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
return adapter.get(key, preferences);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change this preference's stored value to {@code value}. A value of {@code null} will delete the
|
|
||||||
* preference.
|
|
||||||
*/
|
|
||||||
public void set(@Nullable T value) {
|
|
||||||
SharedPreferences.Editor editor = preferences.edit();
|
|
||||||
if (value == null) {
|
|
||||||
editor.remove(key);
|
|
||||||
} else {
|
|
||||||
adapter.set(key, value, editor);
|
|
||||||
}
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns true if this preference has a stored value. */
|
|
||||||
public boolean isSet() {
|
|
||||||
return preferences.contains(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Delete the stored value for this preference, if any. */
|
|
||||||
public void delete() {
|
|
||||||
set(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observe changes to this preference. The current value or {@link #defaultValue()} will be
|
|
||||||
* emitted on first subscribe.
|
|
||||||
*/
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Observable<T> asObservable() {
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An action which stores a new value for this preference. Passing {@code null} will delete the
|
|
||||||
* preference.
|
|
||||||
*/
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Action1<? super T> asAction() {
|
|
||||||
return (Action1<T>) this::set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 Prateek Srivastava
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
This file has been modified after being copied from it's original source.
|
|
||||||
*/
|
|
||||||
package com.f2prateek.rx.preferences;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
|
||||||
import android.support.annotation.CheckResult;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Set;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.subscriptions.Subscriptions;
|
|
||||||
|
|
||||||
import static android.os.Build.VERSION_CODES.HONEYCOMB;
|
|
||||||
import static com.f2prateek.rx.preferences.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
/** A factory for reactive {@link Preference} objects. */
|
|
||||||
public final class RxSharedPreferences {
|
|
||||||
private static final Float DEFAULT_FLOAT = 0f;
|
|
||||||
private static final Integer DEFAULT_INTEGER = 0;
|
|
||||||
private static final Boolean DEFAULT_BOOLEAN = Boolean.FALSE;
|
|
||||||
private static final Long DEFAULT_LONG = 0L;
|
|
||||||
|
|
||||||
/** Create an instance of {@link RxSharedPreferences} for {@code preferences}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public static RxSharedPreferences create(@NonNull SharedPreferences preferences) {
|
|
||||||
checkNotNull(preferences, "preferences == null");
|
|
||||||
return new RxSharedPreferences(preferences);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final SharedPreferences preferences;
|
|
||||||
private final Observable<String> keyChanges;
|
|
||||||
|
|
||||||
private RxSharedPreferences(final SharedPreferences preferences) {
|
|
||||||
this.preferences = preferences;
|
|
||||||
this.keyChanges = Observable.create((Observable.OnSubscribe<String>) subscriber -> {
|
|
||||||
final OnSharedPreferenceChangeListener listener = (preferences1, key) -> subscriber.onNext(key);
|
|
||||||
|
|
||||||
preferences.registerOnSharedPreferenceChangeListener(listener);
|
|
||||||
|
|
||||||
subscriber.add(Subscriptions.create(() -> preferences.unregisterOnSharedPreferenceChangeListener(listener)));
|
|
||||||
}).share();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a boolean preference for {@code key}. Default is {@code false}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Preference<Boolean> getBoolean(@NonNull String key) {
|
|
||||||
return getBoolean(key, DEFAULT_BOOLEAN);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a boolean preference for {@code key} with a default of {@code defaultValue}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Preference<Boolean> getBoolean(@NonNull String key, @Nullable Boolean defaultValue) {
|
|
||||||
checkNotNull(key, "key == null");
|
|
||||||
return new Preference<>(preferences, key, defaultValue, BooleanAdapter.INSTANCE, keyChanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create an enum preference for {@code key}. Default is {@code null}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public <T extends Enum<T>> Preference<T> getEnum(@NonNull String key,
|
|
||||||
@NonNull Class<T> enumClass) {
|
|
||||||
return getEnum(key, null, enumClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create an enum preference for {@code key} with a default of {@code defaultValue}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public <T extends Enum<T>> Preference<T> getEnum(@NonNull String key, @Nullable T defaultValue,
|
|
||||||
@NonNull Class<T> enumClass) {
|
|
||||||
checkNotNull(key, "key == null");
|
|
||||||
checkNotNull(enumClass, "enumClass == null");
|
|
||||||
Preference.Adapter<T> adapter = new EnumAdapter<>(enumClass);
|
|
||||||
return new Preference<>(preferences, key, defaultValue, adapter, keyChanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a float preference for {@code key}. Default is {@code 0}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Preference<Float> getFloat(@NonNull String key) {
|
|
||||||
return getFloat(key, DEFAULT_FLOAT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a float preference for {@code key} with a default of {@code defaultValue}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Preference<Float> getFloat(@NonNull String key, @Nullable Float defaultValue) {
|
|
||||||
checkNotNull(key, "key == null");
|
|
||||||
return new Preference<>(preferences, key, defaultValue, FloatAdapter.INSTANCE, keyChanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create an integer preference for {@code key}. Default is {@code 0}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Preference<Integer> getInteger(@NonNull String key) {
|
|
||||||
//noinspection UnnecessaryBoxing
|
|
||||||
return getInteger(key, DEFAULT_INTEGER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create an integer preference for {@code key} with a default of {@code defaultValue}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Preference<Integer> getInteger(@NonNull String key, @Nullable Integer defaultValue) {
|
|
||||||
checkNotNull(key, "key == null");
|
|
||||||
return new Preference<>(preferences, key, defaultValue, IntegerAdapter.INSTANCE, keyChanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a long preference for {@code key}. Default is {@code 0}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Preference<Long> getLong(@NonNull String key) {
|
|
||||||
//noinspection UnnecessaryBoxing
|
|
||||||
return getLong(key, DEFAULT_LONG);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a long preference for {@code key} with a default of {@code defaultValue}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Preference<Long> getLong(@NonNull String key, @Nullable Long defaultValue) {
|
|
||||||
checkNotNull(key, "key == null");
|
|
||||||
return new Preference<>(preferences, key, defaultValue, LongAdapter.INSTANCE, keyChanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a preference of type {@code T} for {@code key}. Default is {@code null}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public <T> Preference<T> getObject(@NonNull String key, @NonNull Preference.Adapter<T> adapter) {
|
|
||||||
return getObject(key, null, adapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a preference for type {@code T} for {@code key} with a default of {@code defaultValue}.
|
|
||||||
*/
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public <T> Preference<T> getObject(@NonNull String key, @Nullable T defaultValue,
|
|
||||||
@NonNull Preference.Adapter<T> adapter) {
|
|
||||||
checkNotNull(key, "key == null");
|
|
||||||
checkNotNull(adapter, "adapter == null");
|
|
||||||
return new Preference<>(preferences, key, defaultValue, adapter, keyChanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a string preference for {@code key}. Default is {@code null}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Preference<String> getString(@NonNull String key) {
|
|
||||||
return getString(key, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a string preference for {@code key} with a default of {@code defaultValue}. */
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Preference<String> getString(@NonNull String key, @Nullable String defaultValue) {
|
|
||||||
checkNotNull(key, "key == null");
|
|
||||||
return new Preference<>(preferences, key, defaultValue, StringAdapter.INSTANCE, keyChanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a string set preference for {@code key}. Default is an empty set. */
|
|
||||||
@TargetApi(HONEYCOMB)
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Preference<Set<String>> getStringSet(@NonNull String key) {
|
|
||||||
return getStringSet(key, Collections.emptySet());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a string set preference for {@code key} with a default of {@code defaultValue}. */
|
|
||||||
@TargetApi(HONEYCOMB)
|
|
||||||
@CheckResult @NonNull
|
|
||||||
public Preference<Set<String>> getStringSet(@NonNull String key,
|
|
||||||
@NonNull Set<String> defaultValue) {
|
|
||||||
checkNotNull(key, "key == null");
|
|
||||||
return new Preference<>(preferences, key, defaultValue, StringSetAdapter.INSTANCE, keyChanges);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.f2prateek.rx.preferences;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
final class StringAdapter implements Preference.Adapter<String> {
|
|
||||||
static final StringAdapter INSTANCE = new StringAdapter();
|
|
||||||
|
|
||||||
@Override public String get(@NonNull String key, @NonNull SharedPreferences preferences) {
|
|
||||||
return preferences.getString(key, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void set(@NonNull String key, @NonNull String value,
|
|
||||||
@NonNull SharedPreferences.Editor editor) {
|
|
||||||
editor.putString(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package com.f2prateek.rx.preferences;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static android.os.Build.VERSION_CODES.HONEYCOMB;
|
|
||||||
|
|
||||||
@TargetApi(HONEYCOMB)
|
|
||||||
final class StringSetAdapter implements Preference.Adapter<Set<String>> {
|
|
||||||
static final StringSetAdapter INSTANCE = new StringSetAdapter();
|
|
||||||
|
|
||||||
@Override public Set<String> get(@NonNull String key, @NonNull SharedPreferences preferences) {
|
|
||||||
return preferences.getStringSet(key, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void set(@NonNull String key, @NonNull Set<String> value,
|
|
||||||
@NonNull SharedPreferences.Editor editor) {
|
|
||||||
editor.putStringSet(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.github.pwittchen.reactivenetwork.library
|
|
||||||
|
|
||||||
import android.net.NetworkInfo
|
|
||||||
|
|
||||||
class Connectivity {
|
|
||||||
val state = NetworkInfo.State.CONNECTED
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package com.github.pwittchen.reactivenetwork.library
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import rx.Observable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by nulldev on 12/29/16.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class ReactiveNetwork {
|
|
||||||
companion object {
|
|
||||||
fun observeNetworkConnectivity(context: Context) = Observable.just(Connectivity())!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -102,7 +102,7 @@ sourceSets {
|
|||||||
|
|
||||||
// should be bumped with each stable release
|
// should be bumped with each stable release
|
||||||
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.4.4"
|
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.4.4"
|
||||||
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r22"
|
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r23"
|
||||||
|
|
||||||
// counts commit count on master
|
// counts commit count on master
|
||||||
val tachideskRevision = runCatching {
|
val tachideskRevision = runCatching {
|
||||||
|
|||||||
@@ -1,30 +1,13 @@
|
|||||||
/*
|
Copyright 2015 Javier Tomás
|
||||||
Copyright 2014 Prateek Srivastava
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
This file may have been modified after being copied from it's original source.
|
|
||||||
*/
|
|
||||||
package com.f2prateek.rx.preferences;
|
|
||||||
|
|
||||||
final class Preconditions {
|
|
||||||
static void checkNotNull(Object o, String message) {
|
|
||||||
if (o == null) {
|
|
||||||
throw new NullPointerException(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Preconditions() {
|
|
||||||
throw new AssertionError("No instances");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ interface AnimeCatalogueSource : AnimeSource {
|
|||||||
/**
|
/**
|
||||||
* An ISO 639-1 compliant language code (two letters in lower case).
|
* An ISO 639-1 compliant language code (two letters in lower case).
|
||||||
*/
|
*/
|
||||||
val lang: String
|
override val lang: String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the source has support for latest updates.
|
* Whether the source has support for latest updates.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.animesource
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,6 +20,9 @@ interface AnimeSource {
|
|||||||
*/
|
*/
|
||||||
val name: String
|
val name: String
|
||||||
|
|
||||||
|
val lang: String
|
||||||
|
get() = ""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable with the updated details for a anime.
|
* Returns an observable with the updated details for a anime.
|
||||||
*
|
*
|
||||||
@@ -36,12 +40,12 @@ interface AnimeSource {
|
|||||||
fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>>
|
fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable with a link for the episode of an anime.
|
* Returns an observable with a list of video for the episode of an anime.
|
||||||
*
|
*
|
||||||
* @param episode the episode to get the link for.
|
* @param episode the episode to get the link for.
|
||||||
*/
|
*/
|
||||||
// @Deprecated("Use getEpisodeList instead")
|
// @Deprecated("Use getEpisodeList instead")
|
||||||
fun fetchEpisodeLink(episode: SEpisode): Observable<String>
|
fun fetchVideoList(episode: SEpisode): Observable<List<Video>>
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * [1.x API] Get the updated details for a anime.
|
// * [1.x API] Get the updated details for a anime.
|
||||||
@@ -74,4 +78,4 @@ interface AnimeSource {
|
|||||||
|
|
||||||
// fun AnimeSource.icon(): Drawable? = Injekt.get<AnimeExtensionManager>().getAppIconForSource(this)
|
// fun AnimeSource.icon(): Drawable? = Injekt.get<AnimeExtensionManager>().getAppIconForSource(this)
|
||||||
|
|
||||||
// fun AnimeSource.getPreferenceKey(): String = "source_$id"
|
fun AnimeSource.getPreferenceKey(): String = "source_$id"
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.animesource
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
|
||||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
|
||||||
import rx.Observable
|
|
||||||
|
|
||||||
open class AnimeSourceManager(private val context: Context) {
|
|
||||||
|
|
||||||
private val sourcesMap = mutableMapOf<Long, AnimeSource>()
|
|
||||||
|
|
||||||
private val stubSourcesMap = mutableMapOf<Long, StubSource>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
createInternalSources().forEach { registerSource(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun get(sourceKey: Long): AnimeSource? {
|
|
||||||
return sourcesMap[sourceKey]
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getOrStub(sourceKey: Long): AnimeSource {
|
|
||||||
return sourcesMap[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) {
|
|
||||||
StubSource(sourceKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getOnlineSources() = sourcesMap.values.filterIsInstance<AnimeHttpSource>()
|
|
||||||
|
|
||||||
fun getCatalogueSources() = sourcesMap.values.filterIsInstance<AnimeCatalogueSource>()
|
|
||||||
|
|
||||||
internal fun registerSource(source: AnimeSource) {
|
|
||||||
if (!sourcesMap.containsKey(source.id)) {
|
|
||||||
sourcesMap[source.id] = source
|
|
||||||
}
|
|
||||||
if (!stubSourcesMap.containsKey(source.id)) {
|
|
||||||
stubSourcesMap[source.id] = StubSource(source.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun unregisterSource(source: AnimeSource) {
|
|
||||||
sourcesMap.remove(source.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createInternalSources(): List<AnimeSource> = listOf(
|
|
||||||
// LocalAnimeSource(context)
|
|
||||||
)
|
|
||||||
|
|
||||||
inner class StubSource(override val id: Long) : AnimeSource {
|
|
||||||
|
|
||||||
override val name: String
|
|
||||||
get() = id.toString()
|
|
||||||
|
|
||||||
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
|
|
||||||
return Observable.error(getSourceNotInstalledException())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchEpisodeList(anime: SAnime): Observable<List<SEpisode>> {
|
|
||||||
return Observable.error(getSourceNotInstalledException())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchEpisodeLink(episode: SEpisode): Observable<String> {
|
|
||||||
return Observable.error(getSourceNotInstalledException())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getSourceNotInstalledException(): Exception {
|
|
||||||
// return Exception(context.getString(R.string.source_not_installed, id.toString()))
|
|
||||||
return Exception("source not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.model
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
|
// import tachiyomi.animesource.model.AnimeInfo
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
interface SAnime : Serializable {
|
interface SAnime : Serializable {
|
||||||
@@ -23,9 +24,7 @@ interface SAnime : Serializable {
|
|||||||
var initialized: Boolean
|
var initialized: Boolean
|
||||||
|
|
||||||
fun copyFrom(other: SAnime) {
|
fun copyFrom(other: SAnime) {
|
||||||
if (other.title != null) {
|
title = other.title
|
||||||
title = other.title
|
|
||||||
}
|
|
||||||
|
|
||||||
if (other.author != null) {
|
if (other.author != null) {
|
||||||
author = other.author
|
author = other.author
|
||||||
@@ -65,3 +64,30 @@ interface SAnime : Serializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fun SAnime.toAnimeInfo(): AnimeInfo {
|
||||||
|
// return AnimeInfo(
|
||||||
|
// key = this.url,
|
||||||
|
// title = this.title,
|
||||||
|
// artist = this.artist ?: "",
|
||||||
|
// author = this.author ?: "",
|
||||||
|
// description = this.description ?: "",
|
||||||
|
// genres = this.genre?.split(", ") ?: emptyList(),
|
||||||
|
// status = this.status,
|
||||||
|
// cover = this.thumbnail_url ?: ""
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fun AnimeInfo.toSAnime(): SAnime {
|
||||||
|
// val animeInfo = this
|
||||||
|
// return SAnime.create().apply {
|
||||||
|
// url = animeInfo.key
|
||||||
|
// title = animeInfo.title
|
||||||
|
// artist = animeInfo.artist
|
||||||
|
// author = animeInfo.author
|
||||||
|
// description = animeInfo.description
|
||||||
|
// genre = animeInfo.genres.joinToString(", ")
|
||||||
|
// status = animeInfo.status
|
||||||
|
// thumbnail_url = animeInfo.cover
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.animesource.model
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
|
// import tachiyomi.animesource.model.EpisodeInfo
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
interface SEpisode : Serializable {
|
interface SEpisode : Serializable {
|
||||||
@@ -28,3 +29,24 @@ interface SEpisode : Serializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fun SEpisode.toEpisodeInfo(): EpisodeInfo {
|
||||||
|
// return EpisodeInfo(
|
||||||
|
// dateUpload = this.date_upload,
|
||||||
|
// key = this.url,
|
||||||
|
// name = this.name,
|
||||||
|
// number = this.episode_number,
|
||||||
|
// scanlator = this.scanlator ?: ""
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun EpisodeInfo.toSEpisode(): SEpisode {
|
||||||
|
// val episode = this
|
||||||
|
// return SEpisode.create().apply {
|
||||||
|
// url = episode.key
|
||||||
|
// name = episode.name
|
||||||
|
// date_upload = episode.dateUpload
|
||||||
|
// episode_number = episode.number
|
||||||
|
// scanlator = episode.scanlator
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import eu.kanade.tachiyomi.network.ProgressListener
|
||||||
|
import rx.subjects.Subject
|
||||||
|
// import tachiyomi.animesource.model.VideoUrl
|
||||||
|
|
||||||
|
open class Video(
|
||||||
|
val url: String = "",
|
||||||
|
val quality: String = "",
|
||||||
|
var videoUrl: String? = null,
|
||||||
|
@Transient var uri: Uri? = null // Deprecated but can't be deleted due to extensions
|
||||||
|
) : ProgressListener {
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
@Volatile
|
||||||
|
var status: Int = 0
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
statusSubject?.onNext(value)
|
||||||
|
statusCallback?.invoke(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
@Volatile
|
||||||
|
var progress: Int = 0
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
statusCallback?.invoke(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private var statusSubject: Subject<Int, Int>? = null
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private var statusCallback: ((Video) -> Unit)? = null
|
||||||
|
|
||||||
|
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
|
||||||
|
progress = if (contentLength > 0) {
|
||||||
|
(100 * bytesRead / contentLength).toInt()
|
||||||
|
} else {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setStatusSubject(subject: Subject<Int, Int>?) {
|
||||||
|
this.statusSubject = subject
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setStatusCallback(f: ((Video) -> Unit)?) {
|
||||||
|
statusCallback = f
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val QUEUE = 0
|
||||||
|
const val LOAD_VIDEO = 1
|
||||||
|
const val DOWNLOAD_IMAGE = 2
|
||||||
|
const val READY = 3
|
||||||
|
const val ERROR = 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fun Video.toVideoUrl(): VideoUrl {
|
||||||
|
// return VideoUrl(
|
||||||
|
// url = this.videoUrl ?: this.url
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun VideoUrl.toVideo(index: Int): Video {
|
||||||
|
// return Video(
|
||||||
|
// videoUrl = this.url
|
||||||
|
// )
|
||||||
|
// }
|
||||||
@@ -5,11 +5,11 @@ import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
|||||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.network.newCallWithProgress
|
import eu.kanade.tachiyomi.network.newCallWithProgress
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
@@ -218,14 +218,6 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchEpisodeLink(episode: SEpisode): Observable<String> {
|
|
||||||
return client.newCall(episodeLinkRequest(episode))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map { response ->
|
|
||||||
episodeLinkParse(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the request for updating the episode list. Override only if it's needed to override
|
* Returns the request for updating the episode list. Override only if it's needed to override
|
||||||
* the url, send different headers or request method like POST.
|
* the url, send different headers or request method like POST.
|
||||||
@@ -236,16 +228,6 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
|||||||
return GET(baseUrl + anime.url, headers)
|
return GET(baseUrl + anime.url, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the request for getting the episode link. Override only if it's needed to override
|
|
||||||
* the url, send different headers or request method like POST.
|
|
||||||
*
|
|
||||||
* @param episode the episode to look for links.
|
|
||||||
*/
|
|
||||||
protected open fun episodeLinkRequest(episode: SEpisode): Request {
|
|
||||||
return GET(baseUrl + episode.url, headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a list of episodes.
|
* Parses the response from the site and returns a list of episodes.
|
||||||
*
|
*
|
||||||
@@ -254,19 +236,25 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
|||||||
protected abstract fun episodeListParse(response: Response): List<SEpisode>
|
protected abstract fun episodeListParse(response: Response): List<SEpisode>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a list of episodes.
|
* Returns an observable with the page list for a chapter.
|
||||||
*
|
*
|
||||||
* @param response the response from the site.
|
* @param chapter the chapter whose page list has to be fetched.
|
||||||
*/
|
*/
|
||||||
protected abstract fun episodeLinkParse(response: Response): String
|
override fun fetchVideoList(episode: SEpisode): Observable<List<Video>> {
|
||||||
|
return client.newCall(videoListRequest(episode))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
videoListParse(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the request for getting the page list. Override only if it's needed to override the
|
* Returns the request for getting the episode link. Override only if it's needed to override
|
||||||
* url, send different headers or request method like POST.
|
* the url, send different headers or request method like POST.
|
||||||
*
|
*
|
||||||
* @param episode the episode whose page list has to be fetched.
|
* @param episode the episode to look for links.
|
||||||
*/
|
*/
|
||||||
protected open fun pageListRequest(episode: SEpisode): Request {
|
protected open fun videoListRequest(episode: SEpisode): Request {
|
||||||
return GET(baseUrl + episode.url, headers)
|
return GET(baseUrl + episode.url, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +263,7 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
|||||||
*
|
*
|
||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
protected abstract fun pageListParse(response: Response): List<Page>
|
protected abstract fun videoListParse(response: Response): List<Video>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable with the page containing the source url of the image. If there's any
|
* Returns an observable with the page containing the source url of the image. If there's any
|
||||||
@@ -283,20 +271,20 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
|||||||
*
|
*
|
||||||
* @param page the page whose source image has to be fetched.
|
* @param page the page whose source image has to be fetched.
|
||||||
*/
|
*/
|
||||||
open fun fetchImageUrl(page: Page): Observable<String> {
|
open fun fetchVideoUrl(video: Video): Observable<String> {
|
||||||
return client.newCall(imageUrlRequest(page))
|
return client.newCall(videoUrlRequest(video))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { imageUrlParse(it) }
|
.map { videoUrlParse(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the request for getting the url to the source image. Override only if it's needed to
|
* Returns the request for getting the url to the source image. Override only if it's needed to
|
||||||
* override the url, send different headers or request method like POST.
|
* override the url, send different headers or request method like POST.
|
||||||
*
|
*
|
||||||
* @param page the episode whose page list has to be fetched
|
* @param page the chapter whose page list has to be fetched
|
||||||
*/
|
*/
|
||||||
protected open fun imageUrlRequest(page: Page): Request {
|
protected open fun videoUrlRequest(video: Video): Request {
|
||||||
return GET(page.url, headers)
|
return GET(video.url, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -304,15 +292,15 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
|||||||
*
|
*
|
||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
protected abstract fun imageUrlParse(response: Response): String
|
protected abstract fun videoUrlParse(response: Response): String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable with the response of the source image.
|
* Returns an observable with the response of the source image.
|
||||||
*
|
*
|
||||||
* @param page the page whose source image has to be downloaded.
|
* @param page the page whose source image has to be downloaded.
|
||||||
*/
|
*/
|
||||||
fun fetchImage(page: Page): Observable<Response> {
|
fun fetchVideo(video: Video): Observable<Response> {
|
||||||
return client.newCallWithProgress(imageRequest(page), page)
|
return client.newCallWithProgress(videoRequest(video), video)
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,10 +308,10 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
|
|||||||
* Returns the request for getting the source image. Override only if it's needed to override
|
* Returns the request for getting the source image. Override only if it's needed to override
|
||||||
* the url, send different headers or request method like POST.
|
* the url, send different headers or request method like POST.
|
||||||
*
|
*
|
||||||
* @param page the episode whose page list has to be fetched
|
* @param video the video whose link has to be fetched
|
||||||
*/
|
*/
|
||||||
protected open fun imageRequest(page: Page): Request {
|
protected open fun videoRequest(video: Video): Request {
|
||||||
return GET(page.imageUrl!!, headers)
|
return GET(video.videoUrl!!, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
package eu.kanade.tachiyomi.source.online
|
package eu.kanade.tachiyomi.animesource.online
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
fun AnimeHttpSource.getImageUrl(page: Page): Observable<Page> {
|
fun AnimeHttpSource.getVideoUrl(video: Video): Observable<Video> {
|
||||||
page.status = Page.LOAD_PAGE
|
video.status = Video.LOAD_VIDEO
|
||||||
return fetchImageUrl(page)
|
return fetchVideoUrl(video)
|
||||||
.doOnError { page.status = Page.ERROR }
|
.doOnError { video.status = Video.ERROR }
|
||||||
.onErrorReturn { null }
|
.onErrorReturn { null }
|
||||||
.doOnNext { page.imageUrl = it }
|
.doOnNext { video.videoUrl = it }
|
||||||
.map { page }
|
.map { video }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AnimeHttpSource.fetchAllImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
|
fun AnimeHttpSource.fetchUrlFromVideo(video: Video): Observable<Video> {
|
||||||
return Observable.from(pages)
|
return Observable.just(video)
|
||||||
.filter { !it.imageUrl.isNullOrEmpty() }
|
.filter { !it.videoUrl.isNullOrEmpty() }
|
||||||
.mergeWith(fetchRemainingImageUrlsFromPageList(pages))
|
.mergeWith(fetchRemainingVideoUrlsFromVideoList(video))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AnimeHttpSource.fetchRemainingImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
|
fun AnimeHttpSource.fetchRemainingVideoUrlsFromVideoList(video: Video): Observable<Video> {
|
||||||
return Observable.from(pages)
|
return Observable.just(video)
|
||||||
.filter { it.imageUrl.isNullOrEmpty() }
|
.filter { it.videoUrl.isNullOrEmpty() }
|
||||||
.concatMap { getImageUrl(it) }
|
.concatMap { getVideoUrl(it) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.animesource.online
|
|||||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
@@ -159,21 +159,6 @@ abstract class ParsedAnimeHttpSource : AnimeHttpSource() {
|
|||||||
*/
|
*/
|
||||||
protected abstract fun episodeListSelector(): String
|
protected abstract fun episodeListSelector(): String
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the response from the site and returns a list of episodes.
|
|
||||||
*
|
|
||||||
* @param response the response from the site.
|
|
||||||
*/
|
|
||||||
override fun episodeLinkParse(response: Response): String {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
return linkFromElement(document.select(episodeLinkSelector()).first())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Jsoup selector that returns a list of [Element] corresponding to each episode.
|
|
||||||
*/
|
|
||||||
protected abstract fun episodeLinkSelector(): String
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a episode from the given element.
|
* Returns a episode from the given element.
|
||||||
*
|
*
|
||||||
@@ -181,36 +166,35 @@ abstract class ParsedAnimeHttpSource : AnimeHttpSource() {
|
|||||||
*/
|
*/
|
||||||
protected abstract fun episodeFromElement(element: Element): SEpisode
|
protected abstract fun episodeFromElement(element: Element): SEpisode
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a episode from the given element.
|
|
||||||
*
|
|
||||||
* @param element an element obtained from [episodeListSelector].
|
|
||||||
*/
|
|
||||||
protected abstract fun linkFromElement(element: Element): String
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns the page list.
|
* Parses the response from the site and returns the page list.
|
||||||
*
|
*
|
||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
override fun videoListParse(response: Response): List<Video> {
|
||||||
return pageListParse(response.asJsoup())
|
val document = response.asJsoup()
|
||||||
|
return document.select(videoListSelector()).map { videoFromElement(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a page list from the given document.
|
* Returns the Jsoup selector that returns a list of [Element] corresponding to each video.
|
||||||
*
|
|
||||||
* @param document the parsed document.
|
|
||||||
*/
|
*/
|
||||||
protected abstract fun pageListParse(document: Document): List<Page>
|
protected abstract fun videoListSelector(): String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the response from the site and returns the absolute url to the source image.
|
* Returns a video from the given element.
|
||||||
|
*
|
||||||
|
* @param element an element obtained from [videoListSelector].
|
||||||
|
*/
|
||||||
|
protected abstract fun videoFromElement(element: Element): Video
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the response from the site and returns the absolute url to the source video.
|
||||||
*
|
*
|
||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun imageUrlParse(response: Response): String {
|
override fun videoUrlParse(response: Response): String {
|
||||||
return imageUrlParse(response.asJsoup())
|
return videoUrlParse(response.asJsoup())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -218,5 +202,5 @@ abstract class ParsedAnimeHttpSource : AnimeHttpSource() {
|
|||||||
*
|
*
|
||||||
* @param document the parsed document.
|
* @param document the parsed document.
|
||||||
*/
|
*/
|
||||||
protected abstract fun imageUrlParse(document: Document): String
|
protected abstract fun videoUrlParse(document: Document): String
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
// import kotlinx.coroutines.suspendCancellableCoroutine
|
// import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import okhttp3.internal.closeQuietly
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Producer
|
import rx.Producer
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
|
import java.io.IOException
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
fun Call.asObservable(): Observable<Response> {
|
fun Call.asObservable(): Observable<Response> {
|
||||||
return Observable.unsafeCreate { subscriber ->
|
return Observable.unsafeCreate { subscriber ->
|
||||||
@@ -48,36 +53,38 @@ fun Call.asObservable(): Observable<Response> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Based on https://github.com/gildor/kotlin-coroutines-okhttp
|
// Based on https://github.com/gildor/kotlin-coroutines-okhttp
|
||||||
// suspend fun Call.await(assertSuccess: Boolean = false): Response {
|
suspend fun Call.await(): Response {
|
||||||
// return suspendCancellableCoroutine { continuation ->
|
return suspendCancellableCoroutine { continuation ->
|
||||||
// enqueue(
|
enqueue(
|
||||||
// object : Callback {
|
object : Callback {
|
||||||
// override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
// if (assertSuccess && !response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
// continuation.resumeWithException(Exception("HTTP error ${response.code}"))
|
continuation.resumeWithException(Exception("HTTP error ${response.code}"))
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// continuation.resume(response)
|
continuation.resume(response) {
|
||||||
// }
|
response.body?.closeQuietly()
|
||||||
//
|
}
|
||||||
// override fun onFailure(call: Call, e: IOException) {
|
}
|
||||||
// // Don't bother with resuming the continuation if it is already cancelled.
|
|
||||||
// if (continuation.isCancelled) return
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
// continuation.resumeWithException(e)
|
// Don't bother with resuming the continuation if it is already cancelled.
|
||||||
// }
|
if (continuation.isCancelled) return
|
||||||
// }
|
continuation.resumeWithException(e)
|
||||||
// )
|
}
|
||||||
//
|
}
|
||||||
// continuation.invokeOnCancellation {
|
)
|
||||||
// try {
|
|
||||||
// cancel()
|
continuation.invokeOnCancellation {
|
||||||
// } catch (ex: Throwable) {
|
try {
|
||||||
// // Ignore cancel exception
|
cancel()
|
||||||
// }
|
} catch (ex: Throwable) {
|
||||||
// }
|
// Ignore cancel exception
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Call.asObservableSuccess(): Observable<Response> {
|
fun Call.asObservableSuccess(): Observable<Response> {
|
||||||
return asObservable()
|
return asObservable()
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.source
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
|
||||||
import rx.Observable
|
|
||||||
|
|
||||||
open class SourceManager(private val context: Context) {
|
|
||||||
|
|
||||||
private val sourcesMap = mutableMapOf<Long, Source>()
|
|
||||||
|
|
||||||
private val stubSourcesMap = mutableMapOf<Long, StubSource>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
createInternalSources().forEach { registerSource(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun get(sourceKey: Long): Source? {
|
|
||||||
return sourcesMap[sourceKey]
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getOrStub(sourceKey: Long): Source {
|
|
||||||
return sourcesMap[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) {
|
|
||||||
StubSource(sourceKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getOnlineSources() = sourcesMap.values.filterIsInstance<HttpSource>()
|
|
||||||
|
|
||||||
fun getCatalogueSources() = sourcesMap.values.filterIsInstance<CatalogueSource>()
|
|
||||||
|
|
||||||
internal fun registerSource(source: Source, overwrite: Boolean = false) {
|
|
||||||
if (overwrite || !sourcesMap.containsKey(source.id)) {
|
|
||||||
sourcesMap[source.id] = source
|
|
||||||
}
|
|
||||||
if (overwrite || !stubSourcesMap.containsKey(source.id)) {
|
|
||||||
stubSourcesMap[source.id] = StubSource(source.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun unregisterSource(source: Source) {
|
|
||||||
sourcesMap.remove(source.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createInternalSources(): List<Source> = listOf(
|
|
||||||
// LocalSource(context)
|
|
||||||
)
|
|
||||||
|
|
||||||
private inner class StubSource(override val id: Long) : Source {
|
|
||||||
|
|
||||||
override val name: String
|
|
||||||
get() = id.toString()
|
|
||||||
|
|
||||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
|
||||||
return Observable.error(getSourceNotInstalledException())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
|
||||||
return Observable.error(getSourceNotInstalledException())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
|
||||||
return Observable.error(getSourceNotInstalledException())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getSourceNotInstalledException(): Exception {
|
|
||||||
// return Exception(context.getString(R.string.source_not_installed, id.toString()))
|
|
||||||
return Exception("source not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,6 +19,7 @@ import org.jetbrains.exposed.sql.update
|
|||||||
import suwayomi.tachidesk.anime.impl.Anime.getAnime
|
import suwayomi.tachidesk.anime.impl.Anime.getAnime
|
||||||
import suwayomi.tachidesk.anime.impl.util.GetAnimeHttpSource.getAnimeHttpSource
|
import suwayomi.tachidesk.anime.impl.util.GetAnimeHttpSource.getAnimeHttpSource
|
||||||
import suwayomi.tachidesk.anime.model.dataclass.EpisodeDataClass
|
import suwayomi.tachidesk.anime.model.dataclass.EpisodeDataClass
|
||||||
|
import suwayomi.tachidesk.anime.model.dataclass.VideoDataClass
|
||||||
import suwayomi.tachidesk.anime.model.table.AnimeTable
|
import suwayomi.tachidesk.anime.model.table.AnimeTable
|
||||||
import suwayomi.tachidesk.anime.model.table.EpisodeTable
|
import suwayomi.tachidesk.anime.model.table.EpisodeTable
|
||||||
import suwayomi.tachidesk.anime.model.table.toDataClass
|
import suwayomi.tachidesk.anime.model.table.toDataClass
|
||||||
@@ -135,7 +136,7 @@ object Episode {
|
|||||||
|
|
||||||
val animeEntry = transaction { AnimeTable.select { AnimeTable.id eq animeId }.first() }
|
val animeEntry = transaction { AnimeTable.select { AnimeTable.id eq animeId }.first() }
|
||||||
val source = getAnimeHttpSource(animeEntry[AnimeTable.sourceReference])
|
val source = getAnimeHttpSource(animeEntry[AnimeTable.sourceReference])
|
||||||
val fetchedLinkUrl = source.fetchEpisodeLink(
|
val fetchedVideos = source.fetchVideoList(
|
||||||
SEpisode.create().also {
|
SEpisode.create().also {
|
||||||
it.url = episode.url
|
it.url = episode.url
|
||||||
it.name = episode.name
|
it.name = episode.name
|
||||||
@@ -154,7 +155,13 @@ object Episode {
|
|||||||
episode.lastPageRead,
|
episode.lastPageRead,
|
||||||
episode.index,
|
episode.index,
|
||||||
episode.episodeCount,
|
episode.episodeCount,
|
||||||
fetchedLinkUrl
|
fetchedVideos.map {
|
||||||
|
VideoDataClass(
|
||||||
|
it.url,
|
||||||
|
it.quality,
|
||||||
|
it.videoUrl,
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ object PackageTools {
|
|||||||
const val METADATA_SOURCE_CLASS = "tachiyomi.animeextension.class"
|
const val METADATA_SOURCE_CLASS = "tachiyomi.animeextension.class"
|
||||||
const val METADATA_SOURCE_FACTORY = "tachiyomi.animeextension.factory"
|
const val METADATA_SOURCE_FACTORY = "tachiyomi.animeextension.factory"
|
||||||
const val METADATA_NSFW = "tachiyomi.animeextension.nsfw"
|
const val METADATA_NSFW = "tachiyomi.animeextension.nsfw"
|
||||||
const val LIB_VERSION_MIN = 10
|
const val LIB_VERSION_MIN = 12
|
||||||
const val LIB_VERSION_MAX = 10
|
const val LIB_VERSION_MAX = 12
|
||||||
|
|
||||||
private const val officialSignature = "50ab1d1e3a20d204d0ad6d334c7691c632e41b98dfa132bf385695fdfa63839c" // jmir1's key
|
private const val officialSignature = "50ab1d1e3a20d204d0ad6d334c7691c632e41b98dfa132bf385695fdfa63839c" // jmir1's key
|
||||||
var trustedSignatures = mutableSetOf<String>() + officialSignature
|
var trustedSignatures = mutableSetOf<String>() + officialSignature
|
||||||
|
|||||||
@@ -31,5 +31,5 @@ data class EpisodeDataClass(
|
|||||||
val episodeCount: Int? = null,
|
val episodeCount: Int? = null,
|
||||||
|
|
||||||
/** used to construct pages in the front-end */
|
/** used to construct pages in the front-end */
|
||||||
val linkUrl: String? = null,
|
val videos: List<VideoDataClass>? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package suwayomi.tachidesk.anime.model.dataclass
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
data class VideoDataClass(
|
||||||
|
val url: String,
|
||||||
|
val quality: String,
|
||||||
|
var videoUrl: String?,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user