mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-04-21 16:01:58 +00:00
Merge branch 'main' into feature/discord-rpc
This commit is contained in:
commit
e290e0ad4a
48 changed files with 2373 additions and 1334 deletions
21
.github/workflows/release.yml
vendored
21
.github/workflows/release.yml
vendored
|
|
@ -258,7 +258,7 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install webkit2gtk-4.1 clang cmake ninja-build pkg-config libgtk-3-dev mpv libmpv-dev dpkg-dev libblkid-dev liblzma-dev fuse rpm imagemagick
|
sudo apt-get install webkit2gtk-4.1 clang cmake ninja-build pkg-config libgtk-3-dev mpv libmpv-dev dpkg-dev libblkid-dev liblzma-dev fuse rpm
|
||||||
wget -O /usr/local/bin/linuxdeploy "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
|
wget -O /usr/local/bin/linuxdeploy "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
|
||||||
chmod +x /usr/local/bin/linuxdeploy
|
chmod +x /usr/local/bin/linuxdeploy
|
||||||
|
|
||||||
|
|
@ -291,11 +291,10 @@ jobs:
|
||||||
# Create fresh AppDir structure
|
# Create fresh AppDir structure
|
||||||
rm -rf AppDir
|
rm -rf AppDir
|
||||||
mkdir -p AppDir/usr/bin
|
mkdir -p AppDir/usr/bin
|
||||||
mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps
|
mkdir -p AppDir/usr/share/icons
|
||||||
# Copy built files
|
# Copy built files
|
||||||
cp -r build/linux/x64/release/bundle/* AppDir/usr/bin/
|
cp -r build/linux/x64/release/bundle/* AppDir/usr/bin/
|
||||||
convert -resize 256x256 assets/app_icons/icon-red.png AppDir/usr/share/icons/hicolor/256x256/apps/mangayomi.png
|
cp -rL linux/packaging/icons/* AppDir/usr/share/icons
|
||||||
convert -resize 512x512 assets/app_icons/icon-red.png AppDir/mangayomi.png
|
|
||||||
# Create desktop file in AppDir root
|
# Create desktop file in AppDir root
|
||||||
cp linux/mangayomi.desktop AppDir/mangayomi.desktop
|
cp linux/mangayomi.desktop AppDir/mangayomi.desktop
|
||||||
# Create AppRun file
|
# Create AppRun file
|
||||||
|
|
@ -312,7 +311,7 @@ jobs:
|
||||||
/usr/local/bin/linuxdeploy \
|
/usr/local/bin/linuxdeploy \
|
||||||
--appdir AppDir \
|
--appdir AppDir \
|
||||||
--desktop-file AppDir/mangayomi.desktop \
|
--desktop-file AppDir/mangayomi.desktop \
|
||||||
--icon-file AppDir/mangayomi.png \
|
--icon-file AppDir/usr/share/icons/hicolor/512x512/apps/mangayomi.png \
|
||||||
--executable AppDir/usr/bin/mangayomi \
|
--executable AppDir/usr/bin/mangayomi \
|
||||||
--output appimage
|
--output appimage
|
||||||
mv $(find . -type f -name "*.AppImage") build/linux/x64/release/Mangayomi-${{ github.ref_name }}-linux.AppImage
|
mv $(find . -type f -name "*.AppImage") build/linux/x64/release/Mangayomi-${{ github.ref_name }}-linux.AppImage
|
||||||
|
|
@ -323,8 +322,8 @@ jobs:
|
||||||
mkdir -p rpm_build/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
mkdir -p rpm_build/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
||||||
cp -r build/linux/x64/release/bundle rpm_build/SOURCES/mangayomi-${VERSION}
|
cp -r build/linux/x64/release/bundle rpm_build/SOURCES/mangayomi-${VERSION}
|
||||||
tar czf rpm_build/SOURCES/mangayomi-${VERSION}.tar.gz -C rpm_build/SOURCES mangayomi-${VERSION}
|
tar czf rpm_build/SOURCES/mangayomi-${VERSION}.tar.gz -C rpm_build/SOURCES mangayomi-${VERSION}
|
||||||
mkdir -p rpm_build/SOURCES/usr/share/icons/hicolor/256x256/apps
|
mkdir -p rpm_build/SOURCES/usr/share/icons
|
||||||
cp assets/app_icons/icon-red.png rpm_build/SOURCES/usr/share/icons/hicolor/256x256/apps/mangayomi.png
|
cp -rL linux/packaging/icons/* rpm_build/SOURCES/usr/share/icons
|
||||||
cp linux/mangayomi.desktop rpm_build/SOURCES/
|
cp linux/mangayomi.desktop rpm_build/SOURCES/
|
||||||
# RPM Spec File
|
# RPM Spec File
|
||||||
cat <<EOF > rpm_build/SPECS/mangayomi.spec
|
cat <<EOF > rpm_build/SPECS/mangayomi.spec
|
||||||
|
|
@ -353,7 +352,12 @@ jobs:
|
||||||
%files
|
%files
|
||||||
/usr/bin/*
|
/usr/bin/*
|
||||||
/usr/share/applications/mangayomi.desktop
|
/usr/share/applications/mangayomi.desktop
|
||||||
|
/usr/share/icons/hicolor/16x16/apps/mangayomi.png
|
||||||
|
/usr/share/icons/hicolor/32x32/apps/mangayomi.png
|
||||||
|
/usr/share/icons/hicolor/64x64/apps/mangayomi.png
|
||||||
|
/usr/share/icons/hicolor/128x128/apps/mangayomi.png
|
||||||
/usr/share/icons/hicolor/256x256/apps/mangayomi.png
|
/usr/share/icons/hicolor/256x256/apps/mangayomi.png
|
||||||
|
/usr/share/icons/hicolor/512x512/apps/mangayomi.png
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
rpmbuild --define "_topdir $(pwd)/rpm_build" -ba rpm_build/SPECS/mangayomi.spec
|
rpmbuild --define "_topdir $(pwd)/rpm_build" -ba rpm_build/SPECS/mangayomi.spec
|
||||||
|
|
@ -418,10 +422,11 @@ jobs:
|
||||||
dpkg-deb -R "$(find dist -name '*.deb' | head -n 1)" extracted_deb
|
dpkg-deb -R "$(find dist -name '*.deb' | head -n 1)" extracted_deb
|
||||||
# Replace desktop file
|
# Replace desktop file
|
||||||
cp -f linux/mangayomi.desktop extracted_deb/usr/share/applications/mangayomi.desktop
|
cp -f linux/mangayomi.desktop extracted_deb/usr/share/applications/mangayomi.desktop
|
||||||
|
# copy all icon sizes
|
||||||
|
cp -rLf linux/packaging/icons/* extracted_deb/usr/share/icons
|
||||||
# Set correct permissions
|
# Set correct permissions
|
||||||
chmod 644 extracted_deb/usr/share/applications/mangayomi.desktop
|
chmod 644 extracted_deb/usr/share/applications/mangayomi.desktop
|
||||||
# Repack the .deb
|
# Repack the .deb
|
||||||
sudo apt-get install -y fakeroot
|
|
||||||
fakeroot dpkg-deb -b extracted_deb dist/Mangayomi-${{ github.ref_name }}-linux.deb
|
fakeroot dpkg-deb -b extracted_deb dist/Mangayomi-${{ github.ref_name }}-linux.deb
|
||||||
|
|
||||||
- name: upload artifact linux deb
|
- name: upload artifact linux deb
|
||||||
|
|
|
||||||
362
go/build_go.sh
Executable file
362
go/build_go.sh
Executable file
|
|
@ -0,0 +1,362 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Go build script for Mangayomi
|
||||||
|
# It supports Linux, macOS, Windows, Android, and iOS builds.
|
||||||
|
# to build, run:
|
||||||
|
# ./build_go.sh [linux|macos|windows|android|ios] [--all]
|
||||||
|
|
||||||
|
set -e # Stop on error
|
||||||
|
|
||||||
|
# Colors for messages
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Colored message functions
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check prerequisites
|
||||||
|
check_prerequisites() {
|
||||||
|
log_info "Checking prerequisites..."
|
||||||
|
|
||||||
|
# Check Go
|
||||||
|
if ! command -v go &> /dev/null; then
|
||||||
|
log_error "Go is not installed. Please install Go 1.22 or newer."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
GO_VERSION=$(go version | grep -o 'go[0-9]\+\.[0-9]\+' | sed 's/go//')
|
||||||
|
log_info "Detected Go version: $GO_VERSION"
|
||||||
|
|
||||||
|
# Check if we're in the go directory
|
||||||
|
if [ ! -f "go.mod" ]; then
|
||||||
|
log_error "go.mod not found. Run this script from the 'go' directory of the Mangayomi project."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build for Linux
|
||||||
|
build_linux() {
|
||||||
|
log_info "Building for Linux..."
|
||||||
|
mkdir -p ../linux/bundle/lib
|
||||||
|
go build -buildmode=c-shared -ldflags="-s -w" -trimpath -o ../linux/bundle/lib/libmtorrentserver.so ./binding/desktop
|
||||||
|
log_success "Linux build completed: ../linux/bundle/lib/libmtorrentserver.so"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build for macOS
|
||||||
|
build_macos() {
|
||||||
|
log_info "Building for macOS..."
|
||||||
|
|
||||||
|
if [[ "$OSTYPE" != "darwin"* ]]; then
|
||||||
|
log_warning "macOS build requested but you're not on macOS. Build might fail."
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p ../macos/Frameworks
|
||||||
|
|
||||||
|
# Build ARM64
|
||||||
|
log_info "Building macOS ARM64..."
|
||||||
|
CGO_ENABLED=1 GOARCH=arm64 go build -buildmode=c-shared -ldflags="-s -w" -trimpath -o libmtorrentserver_arm64.dylib ./binding/desktop
|
||||||
|
|
||||||
|
# Build AMD64
|
||||||
|
log_info "Building macOS AMD64..."
|
||||||
|
CGO_ENABLED=1 GOARCH=amd64 go build -buildmode=c-shared -ldflags="-s -w" -trimpath -o libmtorrentserver_amd64.dylib ./binding/desktop
|
||||||
|
|
||||||
|
# Create universal binary
|
||||||
|
if command -v lipo &> /dev/null; then
|
||||||
|
log_info "Creating universal binary..."
|
||||||
|
lipo -create -output ../macos/Frameworks/libmtorrentserver.dylib libmtorrentserver_arm64.dylib libmtorrentserver_amd64.dylib
|
||||||
|
|
||||||
|
# Clean up intermediate binaries
|
||||||
|
log_info "Cleaning up intermediate binaries..."
|
||||||
|
rm -f libmtorrentserver_arm64.dylib libmtorrentserver_amd64.dylib
|
||||||
|
rm -f libmtorrentserver_arm64.h libmtorrentserver_amd64.h
|
||||||
|
|
||||||
|
log_success "macOS build completed: ../macos/Frameworks/libmtorrentserver.dylib (universal)"
|
||||||
|
else
|
||||||
|
log_warning "lipo not available. Separate binaries are available."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build for Windows
|
||||||
|
build_windows() {
|
||||||
|
log_info "Building for Windows..."
|
||||||
|
|
||||||
|
mkdir -p ../windows
|
||||||
|
|
||||||
|
# Configure environment for Windows
|
||||||
|
export CGO_ENABLED=1
|
||||||
|
export CC=gcc
|
||||||
|
export GOARCH=amd64
|
||||||
|
|
||||||
|
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
|
||||||
|
# Native Windows
|
||||||
|
go build -buildmode=c-shared -ldflags="-s -w -extldflags=-static" -trimpath -o ../windows/libmtorrentserver.dll ./binding/desktop
|
||||||
|
else
|
||||||
|
# Cross-compilation from Unix
|
||||||
|
if command -v x86_64-w64-mingw32-gcc &> /dev/null; then
|
||||||
|
export CC=x86_64-w64-mingw32-gcc
|
||||||
|
export GOOS=windows
|
||||||
|
go build -buildmode=c-shared -ldflags="-s -w -extldflags=-static" -trimpath -o ../windows/libmtorrentserver.dll ./binding/desktop
|
||||||
|
else
|
||||||
|
log_error "Windows cross-compiler not found. Install mingw-w64."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Windows build completed: ../windows/libmtorrentserver.dll"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build for Android
|
||||||
|
build_android() {
|
||||||
|
log_info "Building for Android..."
|
||||||
|
|
||||||
|
# Check gomobile
|
||||||
|
if ! command -v gomobile &> /dev/null; then
|
||||||
|
log_info "Installing gomobile..."
|
||||||
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
|
go install golang.org/x/mobile/cmd/gobind@latest
|
||||||
|
|
||||||
|
# Add GOPATH/bin to PATH if necessary
|
||||||
|
export PATH=$PATH:$(go env GOPATH)/bin
|
||||||
|
|
||||||
|
# Initialize gomobile
|
||||||
|
log_info "Initializing gomobile..."
|
||||||
|
gomobile init -v
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Android NDK
|
||||||
|
if [ -z "$ANDROID_NDK_HOME" ] && [ -z "$ANDROID_NDK_ROOT" ]; then
|
||||||
|
log_warning "ANDROID_NDK_HOME or ANDROID_NDK_ROOT not defined. Android build might fail."
|
||||||
|
log_warning "Install Android Studio and NDK, then set the environment variable."
|
||||||
|
log_warning "Example: export ANDROID_NDK_HOME=/path/to/android/sdk/ndk/25.2.9519653"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Java JDK
|
||||||
|
if ! javac -version &> /dev/null; then
|
||||||
|
log_warning "javac (Java Development Kit) not working. Android build might fail."
|
||||||
|
log_warning "Install Oracle JDK or OpenJDK and configure JAVA_HOME."
|
||||||
|
log_warning "On macOS: brew install openjdk@11"
|
||||||
|
log_warning "Then: export JAVA_HOME=\$(brew --prefix openjdk@11)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
go get -u golang.org/x/mobile
|
||||||
|
|
||||||
|
log_info "Building Android AAR..."
|
||||||
|
|
||||||
|
mkdir -p ../android/app/libs
|
||||||
|
|
||||||
|
# Try Android build with enhanced error handling
|
||||||
|
# Uses -checklinkname=0 and -tags to allow anet v0.0.5 usage (solution inspired by Gopeed)
|
||||||
|
if ! gomobile bind -v -tags mobile -target=android/arm,android/arm64,android/amd64,android/386 -androidapi 21 -ldflags="-s -w -checklinkname=0" -trimpath -o ../android/app/libs/libmtorrentserver.aar ./binding/mobile; then
|
||||||
|
log_error "Android build failed."
|
||||||
|
log_error "Common error: 'invalid reference to net.zoneCache'"
|
||||||
|
log_error ""
|
||||||
|
log_error "Possible solutions:"
|
||||||
|
log_error "1. Install and configure Android NDK correctly:"
|
||||||
|
log_error " - Download Android Studio"
|
||||||
|
log_error " - Install Android NDK via SDK Manager"
|
||||||
|
log_error " - Set ANDROID_NDK_HOME"
|
||||||
|
log_error ""
|
||||||
|
log_error "2. Update Go to latest version:"
|
||||||
|
log_error " - current go version: $(go version)"
|
||||||
|
log_error " - Recommended: Go 1.21 or newer"
|
||||||
|
log_error ""
|
||||||
|
log_error "3. Known issue with github.com/wlynxg/anet:"
|
||||||
|
log_error " - This package uses internal Go APIs"
|
||||||
|
log_error " - Applied solution: replace directive in go.mod"
|
||||||
|
log_error ""
|
||||||
|
log_error "4. Check Java JDK installation:"
|
||||||
|
log_error " - Install JDK: brew install openjdk@11"
|
||||||
|
log_error " - Set JAVA_HOME: export JAVA_HOME=\$(brew --prefix openjdk@11)"
|
||||||
|
log_error ""
|
||||||
|
log_error "5. Alternative - Desktop build only:"
|
||||||
|
log_error " ./build_go.sh linux macos windows"
|
||||||
|
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f ../android/app/libs/libmtorrentserver-sources.jar
|
||||||
|
log_success "Android build completed: ../android/app/libs/libmtorrentserver.aar"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Build for iOS
|
||||||
|
build_ios() {
|
||||||
|
log_info "Building for iOS..."
|
||||||
|
|
||||||
|
if [[ "$OSTYPE" != "darwin"* ]]; then
|
||||||
|
log_error "iOS build is only possible on macOS."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check Xcode
|
||||||
|
if ! command -v xcodebuild &> /dev/null; then
|
||||||
|
log_error "Xcode is not installed."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check gomobile
|
||||||
|
if ! command -v gomobile &> /dev/null; then
|
||||||
|
log_info "Installing gomobile..."
|
||||||
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
|
go install golang.org/x/mobile/cmd/gobind@latest
|
||||||
|
|
||||||
|
# Add GOPATH/bin to PATH if necessary
|
||||||
|
export PATH=$PATH:$(go env GOPATH)/bin
|
||||||
|
|
||||||
|
# Initialize gomobile
|
||||||
|
log_info "Initializing gomobile..."
|
||||||
|
gomobile init -v
|
||||||
|
fi
|
||||||
|
|
||||||
|
go get -u golang.org/x/mobile
|
||||||
|
|
||||||
|
log_info "Building iOS XCFramework..."
|
||||||
|
|
||||||
|
mkdir -p ../ios/Frameworks
|
||||||
|
|
||||||
|
# Uses same optimizations as Android for anet v0.0.5 compatibility
|
||||||
|
gomobile bind -v -tags mobile -target=ios,iossimulator -ldflags="-s -w -checklinkname=0" -trimpath -o ../ios/Frameworks/libmtorrentserver.xcframework ./binding/mobile
|
||||||
|
|
||||||
|
log_success "iOS build completed: ../ios/Frameworks/libmtorrentserver.xcframework"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Show help
|
||||||
|
show_help() {
|
||||||
|
echo "Usage: $0 [OPTIONS] [TARGETS]"
|
||||||
|
echo ""
|
||||||
|
echo "OPTIONS:"
|
||||||
|
echo " -h, --help Show this help"
|
||||||
|
echo " -a, --all Build for all supported platforms"
|
||||||
|
echo ""
|
||||||
|
echo "TARGETS:"
|
||||||
|
echo " linux Build for Linux"
|
||||||
|
echo " macos Build for macOS"
|
||||||
|
echo " windows Build for Windows"
|
||||||
|
echo " android Build for Android"
|
||||||
|
echo " ios Build for iOS"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 linux macos # Build for Linux and macOS"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
BUILD_ALL=false
|
||||||
|
TARGETS=()
|
||||||
|
|
||||||
|
# Parser les arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-a|--all)
|
||||||
|
BUILD_ALL=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
linux|macos|windows|android|ios)
|
||||||
|
TARGETS+=("$1")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown option: $1"
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Main
|
||||||
|
main() {
|
||||||
|
log_info "Starting Go build for Mangayomi"
|
||||||
|
|
||||||
|
check_prerequisites
|
||||||
|
|
||||||
|
if [ "$BUILD_ALL" = true ]; then
|
||||||
|
TARGETS=("linux" "macos" "windows" "android" "ios")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#TARGETS[@]} -eq 0 ]; then
|
||||||
|
log_warning "No target specified. Building for current platform..."
|
||||||
|
case "$OSTYPE" in
|
||||||
|
linux*) TARGETS=("linux") ;;
|
||||||
|
darwin*) TARGETS=("macos") ;;
|
||||||
|
msys*|win32*) TARGETS=("windows") ;;
|
||||||
|
*)
|
||||||
|
log_error "Unsupported platform: $OSTYPE"
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build for each target
|
||||||
|
for target in "${TARGETS[@]}"; do
|
||||||
|
log_info "--- Building for: $target ---"
|
||||||
|
case $target in
|
||||||
|
linux)
|
||||||
|
if build_linux; then
|
||||||
|
log_success "✅ Build $target successful"
|
||||||
|
else
|
||||||
|
log_error "❌ Build $target failed"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
macos)
|
||||||
|
if build_macos; then
|
||||||
|
log_success "✅ Build $target successful"
|
||||||
|
else
|
||||||
|
log_error "❌ Build $target failed"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
windows)
|
||||||
|
if build_windows; then
|
||||||
|
log_success "✅ Build $target successful"
|
||||||
|
else
|
||||||
|
log_error "❌ Build $target failed"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
android)
|
||||||
|
if build_android; then
|
||||||
|
log_success "✅ Build $target successful"
|
||||||
|
else
|
||||||
|
log_error "❌ Build $target failed"
|
||||||
|
log_warning "Android build may fail due to compatibility issues"
|
||||||
|
log_warning "Other platforms should work normally"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
ios)
|
||||||
|
if build_ios; then
|
||||||
|
log_success "✅ Build $target successful"
|
||||||
|
else
|
||||||
|
log_error "❌ Build $target failed"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown target: $target"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo "" # Empty line for readability
|
||||||
|
done
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute main script
|
||||||
|
main "$@"
|
||||||
112
go/go.mod
112
go/go.mod
|
|
@ -1,9 +1,11 @@
|
||||||
module server
|
module server
|
||||||
|
|
||||||
go 1.22
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.24.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/anacrolix/torrent v1.56.1
|
github.com/anacrolix/torrent v1.58.1
|
||||||
github.com/rs/cors v1.11.1
|
github.com/rs/cors v1.11.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -11,38 +13,38 @@ require (
|
||||||
github.com/RoaringBitmap/roaring v1.9.4 // indirect
|
github.com/RoaringBitmap/roaring v1.9.4 // indirect
|
||||||
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect
|
github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect
|
||||||
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
|
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
|
||||||
github.com/anacrolix/chansync v0.5.1 // indirect
|
github.com/anacrolix/chansync v0.6.0 // indirect
|
||||||
github.com/anacrolix/dht/v2 v2.21.1 // indirect
|
github.com/anacrolix/dht/v2 v2.22.1 // indirect
|
||||||
github.com/anacrolix/envpprof v1.3.0 // indirect
|
github.com/anacrolix/envpprof v1.4.0 // indirect
|
||||||
github.com/anacrolix/generics v0.0.2 // indirect
|
github.com/anacrolix/generics v0.0.3 // indirect
|
||||||
github.com/anacrolix/go-libutp v1.3.1 // indirect
|
github.com/anacrolix/go-libutp v1.3.2 // indirect
|
||||||
github.com/anacrolix/log v0.15.2 // indirect
|
github.com/anacrolix/log v0.16.0 // indirect
|
||||||
github.com/anacrolix/missinggo v1.3.0 // indirect
|
github.com/anacrolix/missinggo v1.3.0 // indirect
|
||||||
github.com/anacrolix/missinggo/perf v1.0.0 // indirect
|
github.com/anacrolix/missinggo/perf v1.0.0 // indirect
|
||||||
github.com/anacrolix/missinggo/v2 v2.7.3 // indirect
|
github.com/anacrolix/missinggo/v2 v2.8.0 // indirect
|
||||||
github.com/anacrolix/mmsg v1.0.0 // indirect
|
github.com/anacrolix/mmsg v1.1.1 // indirect
|
||||||
github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7 // indirect
|
github.com/anacrolix/multiless v0.4.0 // indirect
|
||||||
github.com/anacrolix/stm v0.5.0 // indirect
|
github.com/anacrolix/stm v0.5.0 // indirect
|
||||||
github.com/anacrolix/sync v0.5.1 // indirect
|
github.com/anacrolix/sync v0.5.4 // indirect
|
||||||
github.com/anacrolix/upnp v0.1.4 // indirect
|
github.com/anacrolix/upnp v0.1.4 // indirect
|
||||||
github.com/anacrolix/utp v0.2.0 // indirect
|
github.com/anacrolix/utp v0.2.0 // indirect
|
||||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
github.com/benbjohnson/immutable v0.4.3 // indirect
|
github.com/benbjohnson/immutable v0.4.3 // indirect
|
||||||
github.com/bits-and-blooms/bitset v1.14.2 // indirect
|
github.com/bits-and-blooms/bitset v1.22.0 // indirect
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
github.com/cespare/xxhash v1.1.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
github.com/edsrzf/mmap-go v1.2.0 // indirect
|
||||||
github.com/go-llsqlite/adapter v0.1.0 // indirect
|
github.com/go-llsqlite/adapter v0.2.0 // indirect
|
||||||
github.com/go-llsqlite/crawshaw v0.5.5 // indirect
|
github.com/go-llsqlite/crawshaw v0.6.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/huandu/xstrings v1.5.0 // indirect
|
github.com/huandu/xstrings v1.5.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||||
|
|
@ -50,45 +52,47 @@ require (
|
||||||
github.com/multiformats/go-multihash v0.2.3 // indirect
|
github.com/multiformats/go-multihash v0.2.3 // indirect
|
||||||
github.com/multiformats/go-varint v0.0.7 // indirect
|
github.com/multiformats/go-varint v0.0.7 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/pion/datachannel v1.5.9 // indirect
|
github.com/pion/datachannel v1.5.10 // indirect
|
||||||
github.com/pion/dtls/v2 v2.2.12 // indirect
|
github.com/pion/dtls/v3 v3.0.6 // indirect
|
||||||
github.com/pion/ice/v2 v2.3.34 // indirect
|
github.com/pion/ice/v4 v4.0.10 // indirect
|
||||||
github.com/pion/interceptor v0.1.30 // indirect
|
github.com/pion/interceptor v0.1.40 // indirect
|
||||||
github.com/pion/logging v0.2.2 // indirect
|
github.com/pion/logging v0.2.4 // indirect
|
||||||
github.com/pion/mdns v0.0.12 // indirect
|
github.com/pion/mdns/v2 v2.0.7 // indirect
|
||||||
github.com/pion/randutil v0.1.0 // indirect
|
github.com/pion/randutil v0.1.0 // indirect
|
||||||
github.com/pion/rtcp v1.2.14 // indirect
|
github.com/pion/rtcp v1.2.15 // indirect
|
||||||
github.com/pion/rtp v1.8.9 // indirect
|
github.com/pion/rtp v1.8.20 // indirect
|
||||||
github.com/pion/sctp v1.8.33 // indirect
|
github.com/pion/sctp v1.8.39 // indirect
|
||||||
github.com/pion/sdp/v3 v3.0.9 // indirect
|
github.com/pion/sdp/v3 v3.0.14 // indirect
|
||||||
github.com/pion/srtp/v2 v2.0.20 // indirect
|
github.com/pion/srtp/v3 v3.0.6 // indirect
|
||||||
github.com/pion/stun v0.6.1 // indirect
|
github.com/pion/stun/v3 v3.0.0 // indirect
|
||||||
github.com/pion/transport/v2 v2.2.10 // indirect
|
github.com/pion/transport/v3 v3.0.7 // indirect
|
||||||
github.com/pion/turn/v2 v2.1.6 // indirect
|
github.com/pion/turn/v4 v4.0.2 // indirect
|
||||||
github.com/pion/webrtc/v3 v3.3.0 // indirect
|
github.com/pion/webrtc/v4 v4.1.3 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/protolambda/ctxlock v0.1.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect
|
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
github.com/stretchr/testify v1.9.0 // indirect
|
github.com/tidwall/btree v1.8.0 // indirect
|
||||||
github.com/tidwall/btree v1.7.0 // indirect
|
github.com/wlynxg/anet v0.0.5 // indirect
|
||||||
github.com/wlynxg/anet v0.0.4 // indirect
|
go.etcd.io/bbolt v1.4.2 // indirect
|
||||||
go.etcd.io/bbolt v1.3.11 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.29.0 // indirect
|
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||||
golang.org/x/crypto v0.26.0 // indirect
|
golang.org/x/crypto v0.40.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect
|
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
|
||||||
golang.org/x/net v0.28.0 // indirect
|
golang.org/x/mobile v0.0.0-20250711185624-d5bb5ecc55c0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/net v0.42.0 // indirect
|
||||||
golang.org/x/sys v0.24.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
golang.org/x/time v0.6.0 // indirect
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
golang.org/x/time v0.12.0 // indirect
|
||||||
lukechampine.com/blake3 v1.3.0 // indirect
|
lukechampine.com/blake3 v1.4.1 // indirect
|
||||||
modernc.org/libc v1.60.1 // indirect
|
modernc.org/libc v1.66.3 // indirect
|
||||||
modernc.org/mathutil v1.6.0 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.8.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
modernc.org/sqlite v1.32.0 // indirect
|
modernc.org/sqlite v1.38.0 // indirect
|
||||||
zombiezen.com/go/sqlite v1.3.0 // indirect
|
zombiezen.com/go/sqlite v1.4.2 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/wlynxg/anet => github.com/wlynxg/anet v0.0.5
|
||||||
|
|
|
||||||
301
go/go.sum
301
go/go.sum
|
|
@ -26,26 +26,26 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/anacrolix/chansync v0.5.1 h1:j+R9DtotkXm40VFjZ8rJTSJkg2Gv1ldZt8kl96lyJJ0=
|
github.com/anacrolix/chansync v0.6.0 h1:/aQVvZ1yLRhmqEYrr9dC92JwzNBQ/SNnFi4uk+fTkQY=
|
||||||
github.com/anacrolix/chansync v0.5.1/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
|
github.com/anacrolix/chansync v0.6.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
|
||||||
github.com/anacrolix/dht/v2 v2.21.1 h1:s1rKkfLLcmBHKv4v/mtMkIeHIEptzEFiB6xVu54+5/o=
|
github.com/anacrolix/dht/v2 v2.22.1 h1:mgsljPXyA/EWA7uUDSNjx7wf6gsfhppjVIp9auVeR6w=
|
||||||
github.com/anacrolix/dht/v2 v2.21.1/go.mod h1:SDGC+sEs1pnO2sJGYuhvIis7T8749dDHNfcjtdH4e3g=
|
github.com/anacrolix/dht/v2 v2.22.1/go.mod h1:seXRz6HLw8zEnxlysf9ye2eQbrKUmch6PyOHpe/Nb/U=
|
||||||
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
||||||
github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
||||||
github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
|
github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
|
||||||
github.com/anacrolix/envpprof v1.3.0 h1:WJt9bpuT7A/CDCxPOv/eeZqHWlle/Y0keJUvc6tcJDk=
|
github.com/anacrolix/envpprof v1.4.0 h1:QHeIcrgHcRChhnxR8l6rlaLlRQx9zd7Q2NII6Zbt83w=
|
||||||
github.com/anacrolix/envpprof v1.3.0/go.mod h1:7QIG4CaX1uexQ3tqd5+BRa/9e2D02Wcertl6Yh0jCB0=
|
github.com/anacrolix/envpprof v1.4.0/go.mod h1:7QIG4CaX1uexQ3tqd5+BRa/9e2D02Wcertl6Yh0jCB0=
|
||||||
github.com/anacrolix/generics v0.0.0-20230113004304-d6428d516633/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
|
github.com/anacrolix/generics v0.0.0-20230113004304-d6428d516633/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
|
||||||
github.com/anacrolix/generics v0.0.2 h1:UbtD+KntUGxeGYMC4RwhsETieL9ixGdSptJQRhdy7No=
|
github.com/anacrolix/generics v0.0.3 h1:wMkQgQzq0obSy1tMkxDu7Ife7PsegOBWHDRaSW31EnM=
|
||||||
github.com/anacrolix/generics v0.0.2/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
|
github.com/anacrolix/generics v0.0.3/go.mod h1:MN3ve08Z3zSV/rTuX/ouI4lNdlfTxgdafQJiLzyNRB8=
|
||||||
github.com/anacrolix/go-libutp v1.3.1 h1:idJzreNLl+hNjGC3ZnUOjujEaryeOGgkwHLqSGoige0=
|
github.com/anacrolix/go-libutp v1.3.2 h1:WswiaxTIogchbkzNgGHuHRfbrYLpv4o290mlvcx+++M=
|
||||||
github.com/anacrolix/go-libutp v1.3.1/go.mod h1:heF41EC8kN0qCLMokLBVkB8NXiLwx3t8R8810MTNI5o=
|
github.com/anacrolix/go-libutp v1.3.2/go.mod h1:fCUiEnXJSe3jsPG554A200Qv+45ZzIIyGEvE56SHmyA=
|
||||||
github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
||||||
github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
|
||||||
github.com/anacrolix/log v0.13.1/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68=
|
github.com/anacrolix/log v0.13.1/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68=
|
||||||
github.com/anacrolix/log v0.14.2/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY=
|
github.com/anacrolix/log v0.14.2/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY=
|
||||||
github.com/anacrolix/log v0.15.2 h1:LTSf5Wm6Q4GNWPFMBP7NPYV6UBVZzZLKckL+/Lj72Oo=
|
github.com/anacrolix/log v0.16.0 h1:DSuyb5kAJwl3Y0X1TRcStVrTS9ST9b0BHW+7neE4Xho=
|
||||||
github.com/anacrolix/log v0.15.2/go.mod h1:m0poRtlr41mriZlXBQ9SOVZ8yZBkLjOkDhd5Li5pITA=
|
github.com/anacrolix/log v0.16.0/go.mod h1:m0poRtlr41mriZlXBQ9SOVZ8yZBkLjOkDhd5Li5pITA=
|
||||||
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM=
|
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM=
|
||||||
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62/go.mod h1:66cFKPCO7Sl4vbFnAaSq7e4OXtdMhRSBagJGWgmpJbM=
|
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62/go.mod h1:66cFKPCO7Sl4vbFnAaSq7e4OXtdMhRSBagJGWgmpJbM=
|
||||||
github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s=
|
github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s=
|
||||||
|
|
@ -58,25 +58,25 @@ github.com/anacrolix/missinggo/perf v1.0.0 h1:7ZOGYziGEBytW49+KmYGTaNfnwUqP1HBsy
|
||||||
github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ=
|
github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ=
|
||||||
github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY=
|
github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY=
|
||||||
github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA=
|
github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA=
|
||||||
github.com/anacrolix/missinggo/v2 v2.7.3 h1:Ee//CmZBMadeNiYB/hHo9ly2PFOEZ4Fhsbnug3rDAIE=
|
github.com/anacrolix/missinggo/v2 v2.8.0 h1:6pGnVOlR6TWL9JM5Msyezij8YHU3+oHO7r82Eql/kpA=
|
||||||
github.com/anacrolix/missinggo/v2 v2.7.3/go.mod h1:mIEtp9pgaXqt8VQ3NQxFOod/eQ1H0D1XsZzKUQfwtac=
|
github.com/anacrolix/missinggo/v2 v2.8.0/go.mod h1:vVO5FEziQm+NFmJesc7StpkquZk+WJFCaL0Wp//2sa0=
|
||||||
github.com/anacrolix/mmsg v0.0.0-20180515031531-a4a3ba1fc8bb/go.mod h1:x2/ErsYUmT77kezS63+wzZp8E3byYB0gzirM/WMBLfw=
|
github.com/anacrolix/mmsg v1.0.1/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc=
|
||||||
github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg=
|
github.com/anacrolix/mmsg v1.1.1 h1:4ce/3I5kM7qSF6T5A8MOmDCfac3UqYlk5Bzh5XsWebM=
|
||||||
github.com/anacrolix/mmsg v1.0.0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc=
|
github.com/anacrolix/mmsg v1.1.1/go.mod h1:lPCXEN1eDDQtKktdKEzdw+roswx6wWPpeXAl/WpWVDU=
|
||||||
github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7 h1:lOtCD+LzoD1g7bowhYJNR++uV+FyY5bTZXKwnPex9S8=
|
github.com/anacrolix/multiless v0.4.0 h1:lqSszHkliMsZd2hsyrDvHOw4AbYWa+ijQ66LzbjqWjM=
|
||||||
github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7/go.mod h1:zJv1JF9AqdZiHwxqPgjuOZDGWER6nyE48WBCi/OOrMM=
|
github.com/anacrolix/multiless v0.4.0/go.mod h1:zJv1JF9AqdZiHwxqPgjuOZDGWER6nyE48WBCi/OOrMM=
|
||||||
github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg=
|
github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg=
|
||||||
github.com/anacrolix/stm v0.5.0 h1:9df1KBpttF0TzLgDq51Z+TEabZKMythqgx89f1FQJt8=
|
github.com/anacrolix/stm v0.5.0 h1:9df1KBpttF0TzLgDq51Z+TEabZKMythqgx89f1FQJt8=
|
||||||
github.com/anacrolix/stm v0.5.0/go.mod h1:MOwrSy+jCm8Y7HYfMAwPj7qWVu7XoVvjOiYwJmpeB/M=
|
github.com/anacrolix/stm v0.5.0/go.mod h1:MOwrSy+jCm8Y7HYfMAwPj7qWVu7XoVvjOiYwJmpeB/M=
|
||||||
github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk=
|
github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk=
|
||||||
github.com/anacrolix/sync v0.3.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
|
github.com/anacrolix/sync v0.3.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
|
||||||
github.com/anacrolix/sync v0.5.1 h1:FbGju6GqSjzVoTgcXTUKkF041lnZkG5P0C3T5RL3SGc=
|
github.com/anacrolix/sync v0.5.4 h1:yXZLIjXh/G+Rh2mYGCAPmszmF/fvEPadDy7/pPChpKM=
|
||||||
github.com/anacrolix/sync v0.5.1/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
|
github.com/anacrolix/sync v0.5.4/go.mod h1:21cUWerw9eiu/3T3kyoChu37AVO+YFue1/H15qqubS0=
|
||||||
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||||
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||||
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
|
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
|
||||||
github.com/anacrolix/torrent v1.56.1 h1:QeJMOP0NuhpQ5dATsOqEL0vUO85aPMNMGP2FACNt0Eg=
|
github.com/anacrolix/torrent v1.58.1 h1:6FP+KH57b1gyT2CpVL9fEqf9MGJEgh3xw1VA8rI0pW8=
|
||||||
github.com/anacrolix/torrent v1.56.1/go.mod h1:5DMHbeIM1TuC5wTQ99XieKKLiYZYz6iB2lyZpKZEr6w=
|
github.com/anacrolix/torrent v1.58.1/go.mod h1:/7ZdLuHNKgtCE1gjYJCfbtG9JodBcDaF5ip5EUWRtk8=
|
||||||
github.com/anacrolix/upnp v0.1.4 h1:+2t2KA6QOhm/49zeNyeVwDu1ZYS9dB9wfxyVvh/wk7U=
|
github.com/anacrolix/upnp v0.1.4 h1:+2t2KA6QOhm/49zeNyeVwDu1ZYS9dB9wfxyVvh/wk7U=
|
||||||
github.com/anacrolix/upnp v0.1.4/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic=
|
github.com/anacrolix/upnp v0.1.4/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic=
|
||||||
github.com/anacrolix/utp v0.2.0 h1:65Cdmr6q9WSw2KsM+rtJFu7rqDzLl2bdysf4KlNPcFI=
|
github.com/anacrolix/utp v0.2.0 h1:65Cdmr6q9WSw2KsM+rtJFu7rqDzLl2bdysf4KlNPcFI=
|
||||||
|
|
@ -91,8 +91,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/bits-and-blooms/bitset v1.14.2 h1:YXVoyPndbdvcEVcseEovVfp0qjJp7S+i5+xgp/Nfbdc=
|
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
|
||||||
github.com/bits-and-blooms/bitset v1.14.2/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||||
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
|
||||||
|
|
@ -113,8 +113,8 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
|
||||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
|
||||||
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||||
github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
github.com/frankban/quicktest v1.9.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
||||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
|
@ -128,17 +128,19 @@ github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e/go.mod h1:Ogl1T
|
||||||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-llsqlite/adapter v0.1.0 h1:wGSQNsu/rtYeu/lqZNZQMjwUdEF3OW66xTLvsFwJQUw=
|
github.com/go-llsqlite/adapter v0.2.0 h1:6k4dmTSTg1eKIeH+2kBWaoohn9SFNZeg4LWayZweevI=
|
||||||
github.com/go-llsqlite/adapter v0.1.0/go.mod h1:DADrR88ONKPPeSGjFp5iEN55Arx3fi2qXZeKCYDpbmU=
|
github.com/go-llsqlite/adapter v0.2.0/go.mod h1:tcIEbwjdknnizwMsq9ogjMW6246aIjk97cRywjkbqZ0=
|
||||||
github.com/go-llsqlite/crawshaw v0.5.5 h1:sXnRkiV26MBv++lbPbzp+ZzFcTqzVMxftO8yHyFvwUA=
|
github.com/go-llsqlite/crawshaw v0.6.0 h1:3c0p/CU4EFG2zhSkXLwM2Bgt8ZNqwUgA6wimxkxqC1c=
|
||||||
github.com/go-llsqlite/crawshaw v0.5.5/go.mod h1:/YJdV7uBQaYDE0fwe4z3wwJIZBJxdYzd38ICggWqtaE=
|
github.com/go-llsqlite/crawshaw v0.6.0/go.mod h1:/YJdV7uBQaYDE0fwe4z3wwJIZBJxdYzd38ICggWqtaE=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||||
|
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
|
@ -165,10 +167,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
|
@ -179,8 +180,6 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gortc/stun v1.20.0 h1:7FNyWwjRXVByBItBwpnsv6SZ08UzPfmByLjKmOnpb14=
|
|
||||||
github.com/gortc/stun v1.20.0/go.mod h1:/XeODKxk0b1P5pYtdD/ZlOncR7+6XgU4zb9CzJIFJBs=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
|
|
@ -197,8 +196,8 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
|
@ -237,51 +236,38 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
||||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pion/datachannel v1.5.9 h1:LpIWAOYPyDrXtU+BW7X0Yt/vGtYxtXQ8ql7dFfYUVZA=
|
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
|
||||||
github.com/pion/datachannel v1.5.9/go.mod h1:kDUuk4CU4Uxp82NH4LQZbISULkX/HtzKa4P7ldf9izE=
|
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
|
||||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E=
|
||||||
github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk=
|
github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU=
|
||||||
github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
|
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
|
||||||
github.com/pion/ice/v2 v2.3.34 h1:Ic1ppYCj4tUOcPAp76U6F3fVrlSw8A9JtRXLqw6BbUM=
|
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
|
||||||
github.com/pion/ice/v2 v2.3.34/go.mod h1:mBF7lnigdqgtB+YHkaY/Y6s6tsyRyo4u4rPGRuOjUBQ=
|
github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4=
|
||||||
github.com/pion/interceptor v0.1.30 h1:au5rlVHsgmxNi+v/mjOPazbW1SHzfx7/hYOEYQnUcxA=
|
github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic=
|
||||||
github.com/pion/interceptor v0.1.30/go.mod h1:RQuKT5HTdkP2Fi0cuOS5G5WNymTjzXaGF75J4k7z2nc=
|
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
|
||||||
github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
|
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
|
||||||
github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
|
|
||||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||||
github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
||||||
github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE=
|
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
||||||
github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
|
github.com/pion/rtp v1.8.20 h1:8zcyqohadZE8FCBeGdyEvHiclPIezcwRQH9zfapFyYI=
|
||||||
github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
github.com/pion/rtp v1.8.20/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
|
||||||
github.com/pion/rtp v1.8.9 h1:E2HX740TZKaqdcPmf4pw6ZZuG8u5RlMMt+l3dxeu6Wk=
|
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
|
||||||
github.com/pion/rtp v1.8.9/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
|
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
|
||||||
github.com/pion/sctp v1.8.33 h1:dSE4wX6uTJBcNm8+YlMg7lw1wqyKHggsP5uKbdj+NZw=
|
github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI=
|
||||||
github.com/pion/sctp v1.8.33/go.mod h1:beTnqSzewI53KWoG3nqB282oDMGrhNxBdb+JZnkCwRM=
|
github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
|
||||||
github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY=
|
github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4=
|
||||||
github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M=
|
github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY=
|
||||||
github.com/pion/srtp/v2 v2.0.20 h1:HNNny4s+OUmG280ETrCdgFndp4ufx3/uy85EawYEhTk=
|
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
|
||||||
github.com/pion/srtp/v2 v2.0.20/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA=
|
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
|
||||||
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
|
||||||
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
|
||||||
github.com/pion/stun v1.17.3 h1:JioTPckDp7PdfoF1FYSz1/sOWrXimbjyfZzvg2QIinU=
|
|
||||||
github.com/pion/stun v1.17.3/go.mod h1:4iy9kiYvpncdXoYYJoAvZ4YFybb4/gQmZxUNaU2680Y=
|
|
||||||
github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
|
|
||||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
|
||||||
github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
|
||||||
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
|
||||||
github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q=
|
|
||||||
github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E=
|
|
||||||
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
|
||||||
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
||||||
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
||||||
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=
|
||||||
github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc=
|
github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=
|
||||||
github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
|
github.com/pion/webrtc/v4 v4.1.3 h1:YZ67Boj9X/hk190jJZ8+HFGQ6DqSZ/fYP3sLAZv7c3c=
|
||||||
github.com/pion/webrtc/v3 v3.3.0 h1:Rf4u6n6U5t5sUxhYPQk/samzU/oDv7jk6BA5hyO2F9I=
|
github.com/pion/webrtc/v4 v4.1.3/go.mod h1:rsq+zQ82ryfR9vbb0L1umPJ6Ogq7zm8mcn9fcGnxomM=
|
||||||
github.com/pion/webrtc/v3 v3.3.0/go.mod h1:hVmrDJvwhEertRWObeb1xzulzHGeVUoPlWvxdGzcfU0=
|
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
|
@ -305,13 +291,16 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||||
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
|
github.com/protolambda/ctxlock v0.1.0 h1:rCUY3+vRdcdZXqT07iXgyr744J2DU2LCBIXowYAjBCE=
|
||||||
|
github.com/protolambda/ctxlock v0.1.0/go.mod h1:vefhX6rIZH8rsg5ZpOJfEDYQOppZi19SfPiGOFrNnwM=
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||||
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+14TwAoKa61q6dX8jtwOf6DH8=
|
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+14TwAoKa61q6dX8jtwOf6DH8=
|
||||||
|
|
@ -331,7 +320,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
|
@ -340,56 +328,55 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/tidwall/btree v1.8.0 h1:kHHy8hSBauQUe0KPHMFLOt0olAj1nDnkHPJhr8+HFkM=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/tidwall/btree v1.8.0/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A=
|
||||||
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
|
|
||||||
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
|
|
||||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||||
github.com/wlynxg/anet v0.0.4 h1:0de1OFQxnNqAu+x2FAKKCVIrnfGKQbs7FQz++tB0+Uw=
|
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||||
github.com/wlynxg/anet v0.0.4/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
|
||||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
|
||||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
|
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||||
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||||
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||||
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||||
|
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||||
|
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
|
||||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
|
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
|
||||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mobile v0.0.0-20250711185624-d5bb5ecc55c0 h1:Z6EFcPz8e1cx0ge5jWCwqafndPjdsDQf8fk4Kw3pJoI=
|
||||||
|
golang.org/x/mobile v0.0.0-20250711185624-d5bb5ecc55c0/go.mod h1:kqVs191xxTTCd39tk8zK1UD3jyCS1SPrMHTpJ9ujxZg=
|
||||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
|
@ -405,13 +392,10 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
|
@ -423,8 +407,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|
@ -445,20 +429,15 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
|
||||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
|
@ -466,12 +445,11 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|
@ -482,8 +460,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||||
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
@ -505,7 +483,6 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
|
@ -519,31 +496,33 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
||||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
|
||||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
|
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||||
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
|
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
|
||||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||||
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
modernc.org/libc v1.60.1 h1:at373l8IFRTkJIkAU85BIuUoBM4T1b51ds0E1ovPG2s=
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
modernc.org/libc v1.60.1/go.mod h1:xJuobKuNxKH3RUatS7GjR+suWj+5c2K7bi4m/S5arOY=
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
|
||||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
|
||||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s=
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
|
||||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
zombiezen.com/go/sqlite v1.3.0 h1:98g1gnCm+CNz6AuQHu0gqyw7gR2WU3O3PJufDOStpUs=
|
zombiezen.com/go/sqlite v1.4.2 h1:KZXLrBuJ7tKNEm+VJcApLMeQbhmAUOKA5VWS93DfFRo=
|
||||||
zombiezen.com/go/sqlite v1.3.0/go.mod h1:yRl27//s/9aXU3RWs8uFQwjkTG9gYNGEls6+6SvrclY=
|
zombiezen.com/go/sqlite v1.4.2/go.mod h1:5Kd4taTAD4MkBzT25mQ9uaAlLjyR0rFhsR6iINO70jc=
|
||||||
|
|
|
||||||
797
go/server.go
797
go/server.go
|
|
@ -1,6 +1,5 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
//credits: https://github.com/glblduh/StreamRest
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
@ -11,7 +10,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -20,41 +21,156 @@ import (
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var torrentCli *torrent.Client
|
var (
|
||||||
var torrentcliCfg *torrent.ClientConfig
|
torrentCli *torrent.Client
|
||||||
|
torrentcliCfg *torrent.ClientConfig
|
||||||
|
// Cache for torrent metadata
|
||||||
|
torrentCache = sync.Map{}
|
||||||
|
// Worker pool for I/O operations
|
||||||
|
workerPool *WorkerPool
|
||||||
|
)
|
||||||
|
|
||||||
func Start(config *Config) (int, error) {
|
// WorkerPool manages a pool of goroutines for concurrent tasks
|
||||||
|
type WorkerPool struct {
|
||||||
|
workers int
|
||||||
|
jobQueue chan func()
|
||||||
|
quit chan bool
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
torrentcliCfg = torrent.NewDefaultClientConfig()
|
// NewWorkerPool creates a new worker pool
|
||||||
|
func NewWorkerPool(workers int) *WorkerPool {
|
||||||
torrentcliCfg.DataDir = filepath.Clean(config.Path)
|
if workers <= 0 {
|
||||||
|
workers = runtime.NumCPU()
|
||||||
log.Printf("[INFO] Download directory is set to: %s\n", torrentcliCfg.DataDir)
|
|
||||||
|
|
||||||
var torrentCliErr error
|
|
||||||
torrentCli, torrentCliErr = torrent.NewClient(torrentcliCfg)
|
|
||||||
if torrentCliErr != nil {
|
|
||||||
log.Fatalf("[ERROR] Creation of BitTorrent client failed: %s\n", torrentCliErr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsResolve()
|
pool := &WorkerPool{
|
||||||
|
workers: workers,
|
||||||
|
jobQueue: make(chan func(), workers*2),
|
||||||
|
quit: make(chan bool),
|
||||||
|
}
|
||||||
|
|
||||||
sigs := make(chan os.Signal, 1)
|
pool.start()
|
||||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
return pool
|
||||||
go func() {
|
}
|
||||||
<-sigs
|
|
||||||
log.Println("[INFO] Termination detected. Removing torrents")
|
// start starts the workers
|
||||||
for _, t := range torrentCli.Torrents() {
|
func (p *WorkerPool) start() {
|
||||||
log.Printf("[INFO] Removing torrent: [%s]\n", t.Name())
|
for i := 0; i < p.workers; i++ {
|
||||||
t.Drop()
|
p.wg.Add(1)
|
||||||
rmaErr := os.RemoveAll(filepath.Join(torrentcliCfg.DataDir, t.Name()))
|
go p.worker()
|
||||||
if rmaErr != nil {
|
}
|
||||||
log.Printf("[ERROR] Failed to remove files of torrent: [%s]: %s\n", t.Name(), rmaErr)
|
}
|
||||||
}
|
|
||||||
|
// worker executes the jobs
|
||||||
|
func (p *WorkerPool) worker() {
|
||||||
|
defer p.wg.Done()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case job := <-p.jobQueue:
|
||||||
|
job()
|
||||||
|
case <-p.quit:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit submits a job to the pool
|
||||||
|
func (p *WorkerPool) Submit(job func()) {
|
||||||
|
select {
|
||||||
|
case p.jobQueue <- job:
|
||||||
|
default:
|
||||||
|
// If pool is full, execute directly
|
||||||
|
go job()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the pool
|
||||||
|
func (p *WorkerPool) Stop() {
|
||||||
|
close(p.quit)
|
||||||
|
p.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TorrentMetadata caches torrent metadata
|
||||||
|
type TorrentMetadata struct {
|
||||||
|
InfoHash string
|
||||||
|
Name string
|
||||||
|
Files []FileMetadata
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileMetadata contains file information
|
||||||
|
type FileMetadata struct {
|
||||||
|
Path string
|
||||||
|
Size int64
|
||||||
|
StreamURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(config *Config) (int, error) {
|
||||||
|
// Initialize worker pool
|
||||||
|
workerPool = NewWorkerPool(runtime.NumCPU() * 2)
|
||||||
|
|
||||||
|
torrentcliCfg = torrent.NewDefaultClientConfig()
|
||||||
|
torrentcliCfg.DataDir = filepath.Clean(config.Path)
|
||||||
|
|
||||||
|
// Performance optimizations
|
||||||
|
torrentcliCfg.DisableUTP = false
|
||||||
|
torrentcliCfg.NoDHT = false
|
||||||
|
torrentcliCfg.NoDefaultPortForwarding = false
|
||||||
|
torrentcliCfg.DisablePEX = false
|
||||||
|
torrentcliCfg.AcceptPeerConnections = true
|
||||||
|
torrentcliCfg.EstablishedConnsPerTorrent = 80
|
||||||
|
torrentcliCfg.HalfOpenConnsPerTorrent = 25
|
||||||
|
torrentcliCfg.TorrentPeersHighWater = 200
|
||||||
|
torrentcliCfg.TorrentPeersLowWater = 50
|
||||||
|
|
||||||
|
log.Printf("[INFO] Download directory: %s", torrentcliCfg.DataDir)
|
||||||
|
log.Printf("[INFO] Worker pool size: %d", workerPool.workers)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
torrentCli, err = torrent.NewClient(torrentcliCfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("[ERROR] BitTorrent client creation failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimized DNS configuration
|
||||||
|
configureDNS()
|
||||||
|
|
||||||
|
// HTTP server configuration with optimized timeouts
|
||||||
|
mux := setupRoutes()
|
||||||
|
c := configureCORS()
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", config.Address)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
addr := listener.Addr().(*net.TCPAddr)
|
||||||
|
|
||||||
|
// Optimized HTTP server
|
||||||
|
server := &http.Server{
|
||||||
|
Handler: c.Handler(mux),
|
||||||
|
ReadTimeout: 30 * time.Second,
|
||||||
|
WriteTimeout: 30 * time.Second,
|
||||||
|
IdleTimeout: 120 * time.Second,
|
||||||
|
ReadHeaderTimeout: 10 * time.Second,
|
||||||
|
MaxHeaderBytes: 1 << 20, // 1MB
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graceful shutdown handling
|
||||||
|
setupGracefulShutdown(server)
|
||||||
|
|
||||||
|
log.Printf("[INFO] Server listening on %s", addr.AddrPort())
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Printf("[ERROR] Server error: %s", err)
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
return addr.Port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupRoutes() *http.ServeMux {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/torrent/addmagnet", addMagnet)
|
mux.HandleFunc("/torrent/addmagnet", addMagnet)
|
||||||
mux.HandleFunc("/torrent/stream", streamTorrent)
|
mux.HandleFunc("/torrent/stream", streamTorrent)
|
||||||
|
|
@ -63,45 +179,85 @@ func Start(config *Config) (int, error) {
|
||||||
mux.HandleFunc("/torrent/play", playTorrent)
|
mux.HandleFunc("/torrent/play", playTorrent)
|
||||||
mux.HandleFunc("/torrent/add", AddTorrent)
|
mux.HandleFunc("/torrent/add", AddTorrent)
|
||||||
mux.HandleFunc("/", Init)
|
mux.HandleFunc("/", Init)
|
||||||
|
return mux
|
||||||
|
}
|
||||||
|
|
||||||
c := cors.New(cors.Options{
|
func configureCORS() *cors.Cors {
|
||||||
|
return cors.New(cors.Options{
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
AllowedMethods: []string{"GET", "POST", "DELETE"},
|
AllowedMethods: []string{"GET", "POST", "DELETE"},
|
||||||
AllowCredentials: true,
|
AllowCredentials: true,
|
||||||
|
MaxAge: 86400, // 24h cache
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", config.Address)
|
func setupGracefulShutdown(server *http.Server) {
|
||||||
if err != nil {
|
sigs := make(chan os.Signal, 1)
|
||||||
return 0, err
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
}
|
|
||||||
addr := listener.Addr().(*net.TCPAddr)
|
|
||||||
|
|
||||||
log.Printf("[INFO] Listening on %s\n", addr.AddrPort())
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := http.Serve(listener, c.Handler(mux)); err != nil && err != http.ErrServerClosed {
|
<-sigs
|
||||||
panic(err)
|
log.Println("[INFO] Shutdown signal received")
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return addr.Port, nil
|
// Graceful server shutdown
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
|
log.Printf("[ERROR] Server shutdown error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Torrent cleanup
|
||||||
|
cleanupTorrents()
|
||||||
|
|
||||||
|
// Stop worker pool
|
||||||
|
if workerPool != nil {
|
||||||
|
workerPool.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupTorrents() {
|
||||||
|
log.Println("[INFO] Cleaning up torrents...")
|
||||||
|
for _, t := range torrentCli.Torrents() {
|
||||||
|
log.Printf("[INFO] Removing torrent: [%s]", t.Name())
|
||||||
|
t.Drop()
|
||||||
|
if err := os.RemoveAll(filepath.Join(torrentcliCfg.DataDir, t.Name())); err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to remove torrent files [%s]: %s", t.Name(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if torrentCli != nil {
|
||||||
|
torrentCli.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func safenDisplayPath(displayPath string) string {
|
func safenDisplayPath(displayPath string) string {
|
||||||
fileNameArray := strings.Split(displayPath, "/")
|
return strings.ReplaceAll(displayPath, "/", " ")
|
||||||
return strings.Join(fileNameArray, " ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendFilePlaylist(scheme string, host string, infohash string, name string) string {
|
func appendFilePlaylist(scheme, host, infohash, name string) string {
|
||||||
playList := "#EXTINF:-1," + safenDisplayPath(name) + "\n"
|
var sb strings.Builder
|
||||||
playList += scheme + "://" + host + "/torrent/stream?infohash=" + infohash + "&file=" + url.QueryEscape(name) + "\n"
|
sb.WriteString("#EXTINF:-1,")
|
||||||
return playList
|
sb.WriteString(safenDisplayPath(name))
|
||||||
|
sb.WriteString("\n")
|
||||||
|
sb.WriteString(scheme)
|
||||||
|
sb.WriteString("://")
|
||||||
|
sb.WriteString(host)
|
||||||
|
sb.WriteString("/torrent/stream?infohash=")
|
||||||
|
sb.WriteString(infohash)
|
||||||
|
sb.WriteString("&file=")
|
||||||
|
sb.WriteString(url.QueryEscape(name))
|
||||||
|
sb.WriteString("\n")
|
||||||
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func nameCheck(str string, substr string) bool {
|
func nameCheck(str, substr string) bool {
|
||||||
splittedSubStr := strings.Split(substr, " ")
|
strLower := strings.ToLower(str)
|
||||||
for _, curWord := range splittedSubStr {
|
words := strings.Fields(strings.ToLower(substr))
|
||||||
if !strings.Contains(str, curWord) {
|
for _, word := range words {
|
||||||
|
if !strings.Contains(strLower, word) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -109,140 +265,269 @@ func nameCheck(str string, substr string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTorrentFile(files []*torrent.File, filename string, exactName bool) *torrent.File {
|
func getTorrentFile(files []*torrent.File, filename string, exactName bool) *torrent.File {
|
||||||
var tFile *torrent.File = nil
|
if filename == "" && !exactName {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if exactName && file.DisplayPath() == filename {
|
if exactName && file.DisplayPath() == filename {
|
||||||
tFile = file
|
return file
|
||||||
}
|
}
|
||||||
if !exactName && filename != "" && nameCheck(strings.ToLower(file.DisplayPath()), strings.ToLower(filename)) {
|
if !exactName && nameCheck(file.DisplayPath(), filename) {
|
||||||
tFile = file
|
return file
|
||||||
}
|
|
||||||
if tFile != nil {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tFile
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/YouROK/TorrServer/blob/681fc5c343f6d2782dee0c015d2ba2dfd210f88f/server/cmd/main.go#L114
|
// configureDNS optimizes DNS resolution
|
||||||
func dnsResolve() {
|
func configureDNS() {
|
||||||
addrs, err := net.LookupHost("www.google.com")
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
if len(addrs) == 0 {
|
defer cancel()
|
||||||
log.Printf("Check dns failed", addrs, err)
|
|
||||||
|
|
||||||
fn := func(ctx context.Context, network, address string) (net.Conn, error) {
|
addrs, err := net.DefaultResolver.LookupHost(ctx, "www.google.com")
|
||||||
d := net.Dialer{}
|
if len(addrs) == 0 || err != nil {
|
||||||
return d.DialContext(ctx, "udp", "1.1.1.1:53")
|
log.Printf("[WARN] DNS check failed, using Cloudflare DNS: %v", err)
|
||||||
}
|
|
||||||
|
|
||||||
net.DefaultResolver = &net.Resolver{
|
net.DefaultResolver = &net.Resolver{
|
||||||
Dial: fn,
|
PreferGo: true,
|
||||||
|
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
d := net.Dialer{Timeout: 5 * time.Second}
|
||||||
|
return d.DialContext(ctx, "udp", "1.1.1.1:53")
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
addrs, err = net.LookupHost("www.google.com")
|
// Test with Cloudflare DNS
|
||||||
log.Printf("Check cloudflare dns", addrs, err)
|
ctx2, cancel2 := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel2()
|
||||||
|
addrs, err = net.DefaultResolver.LookupHost(ctx2, "www.google.com")
|
||||||
|
log.Printf("[INFO] Cloudflare DNS test: %v, error: %v", len(addrs) > 0, err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Check dns OK", addrs, err)
|
log.Printf("[INFO] DNS check OK: %d addresses", len(addrs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makePlayStreamURL(infohash string, filename string, isStream bool) string {
|
// getCachedTorrentMetadata retrieves metadata from cache
|
||||||
endPoint := "play"
|
func getCachedTorrentMetadata(infoHash string) (*TorrentMetadata, bool) {
|
||||||
|
if cached, ok := torrentCache.Load(infoHash); ok {
|
||||||
|
meta := cached.(*TorrentMetadata)
|
||||||
|
// Cache valid for 5 minutes
|
||||||
|
if time.Since(meta.UpdatedAt) < 5*time.Minute {
|
||||||
|
return meta, true
|
||||||
|
}
|
||||||
|
// Remove expired cache
|
||||||
|
torrentCache.Delete(infoHash)
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheTorrentMetadata caches metadata
|
||||||
|
func cacheTorrentMetadata(t *torrent.Torrent) *TorrentMetadata {
|
||||||
|
meta := &TorrentMetadata{
|
||||||
|
InfoHash: t.InfoHash().String(),
|
||||||
|
Name: t.Name(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Info() != nil {
|
||||||
|
meta.Files = make([]FileMetadata, 0, len(t.Files()))
|
||||||
|
for _, file := range t.Files() {
|
||||||
|
meta.Files = append(meta.Files, FileMetadata{
|
||||||
|
Path: file.DisplayPath(),
|
||||||
|
Size: file.Length(),
|
||||||
|
StreamURL: makePlayStreamURL(meta.InfoHash, file.DisplayPath(), true),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
torrentCache.Store(meta.InfoHash, meta)
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePlayStreamURL(infohash, filename string, isStream bool) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("/torrent/")
|
||||||
if isStream {
|
if isStream {
|
||||||
endPoint = "stream"
|
sb.WriteString("stream")
|
||||||
|
} else {
|
||||||
|
sb.WriteString("play")
|
||||||
}
|
}
|
||||||
URL := "/torrent/" + endPoint + "?infohash=" + infohash
|
sb.WriteString("?infohash=")
|
||||||
|
sb.WriteString(infohash)
|
||||||
if filename != "" {
|
if filename != "" {
|
||||||
URL += "&file=" + url.QueryEscape(filename)
|
sb.WriteString("&file=")
|
||||||
|
sb.WriteString(url.QueryEscape(filename))
|
||||||
}
|
}
|
||||||
return URL
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func httpJSONError(w http.ResponseWriter, error string, code int) {
|
func httpJSONError(w http.ResponseWriter, error string, code int) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(code)
|
w.WriteHeader(code)
|
||||||
if json.NewEncoder(w).Encode(errorRes{
|
if err := json.NewEncoder(w).Encode(errorRes{Error: error}); err != nil {
|
||||||
Error: error,
|
|
||||||
}) != nil {
|
|
||||||
http.Error(w, error, code)
|
http.Error(w, error, code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRequestBody(w http.ResponseWriter, r *http.Request, v any) error {
|
func parseRequestBody(w http.ResponseWriter, r *http.Request, v interface{}) error {
|
||||||
err := json.NewDecoder(r.Body).Decode(v)
|
defer r.Body.Close()
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
decoder.DisallowUnknownFields() // Additional security
|
||||||
|
err := decoder.Decode(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpJSONError(w, "Request JSON body decode error", http.StatusInternalServerError)
|
httpJSONError(w, "Request JSON body decode error", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeJSONResponse(w http.ResponseWriter, v any) {
|
func makeJSONResponse(w http.ResponseWriter, v interface{}) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
err := json.NewEncoder(w).Encode(v)
|
encoder := json.NewEncoder(w)
|
||||||
if err != nil {
|
if err := encoder.Encode(v); err != nil {
|
||||||
httpJSONError(w, "Response JSON body encode error", http.StatusInternalServerError)
|
httpJSONError(w, "Response JSON body encode error", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInfo(t *torrent.Torrent) {
|
// getInfoWithTimeout waits for torrent information with timeout
|
||||||
if t != nil {
|
func getInfoWithTimeout(t *torrent.Torrent, timeout time.Duration) error {
|
||||||
<-t.GotInfo()
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-t.GotInfo():
|
||||||
|
return nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initMagnet(w http.ResponseWriter, magnet string, alldn []string, alltr []string) *torrent.Torrent {
|
func initMagnet(w http.ResponseWriter, magnet string, alldn, alltr []string) *torrent.Torrent {
|
||||||
var t *torrent.Torrent = nil
|
var sb strings.Builder
|
||||||
var err error
|
sb.WriteString(magnet)
|
||||||
magnetString := magnet
|
|
||||||
for _, dn := range alldn {
|
for _, dn := range alldn {
|
||||||
magnetString += "&dn=" + url.QueryEscape(dn)
|
sb.WriteString("&dn=")
|
||||||
|
sb.WriteString(url.QueryEscape(dn))
|
||||||
}
|
}
|
||||||
for _, tr := range alltr {
|
for _, tr := range alltr {
|
||||||
magnetString += "&tr=" + url.QueryEscape(tr)
|
sb.WriteString("&tr=")
|
||||||
|
sb.WriteString(url.QueryEscape(tr))
|
||||||
}
|
}
|
||||||
t, err = torrentCli.AddMagnet(magnetString)
|
|
||||||
|
t, err := torrentCli.AddMagnet(sb.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpJSONError(w, "Torrent add error", http.StatusInternalServerError)
|
httpJSONError(w, "Torrent add error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
getInfo(t)
|
|
||||||
|
// Wait for information with timeout
|
||||||
|
if err := getInfoWithTimeout(t, 30*time.Second); err != nil {
|
||||||
|
log.Printf("[WARN] Timeout waiting for torrent info: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache metadata
|
||||||
|
cacheTorrentMetadata(t)
|
||||||
|
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTorrent(w http.ResponseWriter, infoHash string) *torrent.Torrent {
|
func getTorrent(w http.ResponseWriter, infoHash string) *torrent.Torrent {
|
||||||
var t *torrent.Torrent = nil
|
|
||||||
var tOk bool
|
|
||||||
if len(infoHash) != 40 {
|
if len(infoHash) != 40 {
|
||||||
httpJSONError(w, "InfoHash not valid", http.StatusInternalServerError)
|
httpJSONError(w, "InfoHash not valid", http.StatusBadRequest)
|
||||||
return t
|
return nil
|
||||||
}
|
}
|
||||||
t, tOk = torrentCli.Torrent(metainfo.NewHashFromHex(infoHash))
|
|
||||||
if !tOk {
|
// Check cache first
|
||||||
|
if _, found := getCachedTorrentMetadata(infoHash); found {
|
||||||
|
if t, ok := torrentCli.Torrent(metainfo.NewHashFromHex(infoHash)); ok {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
// Remove from cache if torrent no longer exists
|
||||||
|
torrentCache.Delete(infoHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
t, ok := torrentCli.Torrent(metainfo.NewHashFromHex(infoHash))
|
||||||
|
if !ok {
|
||||||
httpJSONError(w, "Torrent not found", http.StatusNotFound)
|
httpJSONError(w, "Torrent not found", http.StatusNotFound)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
getInfo(t)
|
|
||||||
|
if t == nil {
|
||||||
|
httpJSONError(w, "Torrent is nil", http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for information with timeout
|
||||||
|
if err := getInfoWithTimeout(t, 10*time.Second); err != nil {
|
||||||
|
log.Printf("[WARN] Timeout waiting for torrent info: %s", err)
|
||||||
|
// Don't return nil here, torrent may still be usable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache metadata only if we have the info
|
||||||
|
if t.Info() != nil {
|
||||||
|
cacheTorrentMetadata(t)
|
||||||
|
}
|
||||||
|
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTorrentStats(t *torrent.Torrent) torrentStatsRes {
|
func parseTorrentStats(t *torrent.Torrent) torrentStatsRes {
|
||||||
|
// Protection against nil pointers
|
||||||
|
if t == nil {
|
||||||
|
return torrentStatsRes{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use cache if available
|
||||||
|
if meta, found := getCachedTorrentMetadata(t.InfoHash().String()); found {
|
||||||
|
stats := t.Stats()
|
||||||
|
return torrentStatsRes{
|
||||||
|
InfoHash: meta.InfoHash,
|
||||||
|
Name: meta.Name,
|
||||||
|
TotalPeers: stats.TotalPeers,
|
||||||
|
ActivePeers: stats.ActivePeers,
|
||||||
|
HalfOpenPeers: stats.HalfOpenPeers,
|
||||||
|
PendingPeers: stats.PendingPeers,
|
||||||
|
Files: buildFilesFromCache(meta, t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to original method
|
||||||
var tsRes torrentStatsRes
|
var tsRes torrentStatsRes
|
||||||
|
|
||||||
tsRes.InfoHash = t.InfoHash().String()
|
tsRes.InfoHash = t.InfoHash().String()
|
||||||
tsRes.Name = t.Name()
|
tsRes.Name = t.Name()
|
||||||
tsRes.TotalPeers = t.Stats().TotalPeers
|
|
||||||
tsRes.ActivePeers = t.Stats().ActivePeers
|
stats := t.Stats()
|
||||||
tsRes.HalfOpenPeers = t.Stats().HalfOpenPeers
|
tsRes.TotalPeers = stats.TotalPeers
|
||||||
tsRes.PendingPeers = t.Stats().PendingPeers
|
tsRes.ActivePeers = stats.ActivePeers
|
||||||
|
tsRes.HalfOpenPeers = stats.HalfOpenPeers
|
||||||
|
tsRes.PendingPeers = stats.PendingPeers
|
||||||
|
|
||||||
if t.Info() == nil {
|
if t.Info() == nil {
|
||||||
return tsRes
|
return tsRes
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tFile := range t.Files() {
|
files := t.Files()
|
||||||
|
if files == nil {
|
||||||
|
return tsRes
|
||||||
|
}
|
||||||
|
|
||||||
|
tsRes.Files.OnTorrent = make([]torrentStatsFilesOnTorrent, 0, len(files))
|
||||||
|
|
||||||
|
for _, tFile := range files {
|
||||||
|
if tFile == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
tsRes.Files.OnTorrent = append(tsRes.Files.OnTorrent, torrentStatsFilesOnTorrent{
|
tsRes.Files.OnTorrent = append(tsRes.Files.OnTorrent, torrentStatsFilesOnTorrent{
|
||||||
FileName: tFile.DisplayPath(),
|
FileName: tFile.DisplayPath(),
|
||||||
FileSizeBytes: int(tFile.Length()),
|
FileSizeBytes: int(tFile.Length()),
|
||||||
})
|
})
|
||||||
if tFile.BytesCompleted() != 0 {
|
|
||||||
|
if tFile.BytesCompleted() > 0 {
|
||||||
tsRes.Files.OnDisk = append(tsRes.Files.OnDisk, torrentStatsFilesOnDisk{
|
tsRes.Files.OnDisk = append(tsRes.Files.OnDisk, torrentStatsFilesOnDisk{
|
||||||
FileName: tFile.DisplayPath(),
|
FileName: tFile.DisplayPath(),
|
||||||
StreamURL: makePlayStreamURL(t.InfoHash().String(), tFile.DisplayPath(), true),
|
StreamURL: makePlayStreamURL(t.InfoHash().String(), tFile.DisplayPath(), true),
|
||||||
|
|
@ -252,9 +537,38 @@ func parseTorrentStats(t *torrent.Torrent) torrentStatsRes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update cache only if we have complete info
|
||||||
|
if t.Info() != nil {
|
||||||
|
cacheTorrentMetadata(t)
|
||||||
|
}
|
||||||
|
|
||||||
return tsRes
|
return tsRes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildFilesFromCache(meta *TorrentMetadata, t *torrent.Torrent) torrentStatsFiles {
|
||||||
|
var files torrentStatsFiles
|
||||||
|
files.OnTorrent = make([]torrentStatsFilesOnTorrent, 0, len(meta.Files))
|
||||||
|
|
||||||
|
for _, fileMeta := range meta.Files {
|
||||||
|
files.OnTorrent = append(files.OnTorrent, torrentStatsFilesOnTorrent{
|
||||||
|
FileName: fileMeta.Path,
|
||||||
|
FileSizeBytes: int(fileMeta.Size),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check if file is downloaded (requires access to real torrent)
|
||||||
|
if tFile := getTorrentFile(t.Files(), fileMeta.Path, true); tFile != nil && tFile.BytesCompleted() > 0 {
|
||||||
|
files.OnDisk = append(files.OnDisk, torrentStatsFilesOnDisk{
|
||||||
|
FileName: fileMeta.Path,
|
||||||
|
StreamURL: fileMeta.StreamURL,
|
||||||
|
BytesDownloaded: int(tFile.BytesCompleted()),
|
||||||
|
FileSizeBytes: int(fileMeta.Size),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
func addMagnet(w http.ResponseWriter, r *http.Request) {
|
func addMagnet(w http.ResponseWriter, r *http.Request) {
|
||||||
var amBody addMagnetBody
|
var amBody addMagnetBody
|
||||||
var amRes addMagnetRes
|
var amRes addMagnetRes
|
||||||
|
|
@ -317,108 +631,273 @@ func addMagnet(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func streamTorrent(w http.ResponseWriter, r *http.Request) {
|
func streamTorrent(w http.ResponseWriter, r *http.Request) {
|
||||||
infoHash, ihOk := r.URL.Query()["infohash"]
|
query := r.URL.Query()
|
||||||
fileName, fnOk := r.URL.Query()["file"]
|
infoHashParams, ihOk := query["infohash"]
|
||||||
|
fileNameParams, fnOk := query["file"]
|
||||||
|
|
||||||
if !ihOk || !fnOk {
|
if !ihOk || !fnOk || len(infoHashParams) == 0 || len(fileNameParams) == 0 {
|
||||||
httpJSONError(w, "InfoHash or File is not provided", http.StatusNotFound)
|
httpJSONError(w, "InfoHash or File is not provided", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t := getTorrent(w, infoHash[0])
|
infoHash := infoHashParams[0]
|
||||||
|
fileName := fileNameParams[0]
|
||||||
|
|
||||||
|
t := getTorrent(w, infoHash)
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tFile := getTorrentFile(t.Files(), fileName[0], true)
|
tFile := getTorrentFile(t.Files(), fileName, true)
|
||||||
if tFile == nil {
|
if tFile == nil {
|
||||||
httpJSONError(w, "File not found", http.StatusNotFound)
|
httpJSONError(w, "File not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fileRead := tFile.NewReader()
|
|
||||||
defer fileRead.Close()
|
fileReader := tFile.NewReader()
|
||||||
fileRead.SetReadahead(tFile.Length() / 100)
|
defer fileReader.Close()
|
||||||
http.ServeContent(w, r, tFile.DisplayPath(), time.Now(), fileRead)
|
|
||||||
|
readaheadSize := calculateReadahead(tFile.Length())
|
||||||
|
fileReader.SetReadahead(readaheadSize)
|
||||||
|
|
||||||
|
// Headers for optimized streaming
|
||||||
|
w.Header().Set("Accept-Ranges", "bytes")
|
||||||
|
w.Header().Set("Content-Type", getContentType(fileName))
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||||
|
|
||||||
|
http.ServeContent(w, r, tFile.DisplayPath(), time.Now(), fileReader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculateReadahead calculates optimal readahead size
|
||||||
|
func calculateReadahead(fileSize int64) int64 {
|
||||||
|
switch {
|
||||||
|
case fileSize < 100*1024*1024: // < 100MB
|
||||||
|
return fileSize / 20 // 5%
|
||||||
|
case fileSize < 1024*1024*1024: // < 1GB
|
||||||
|
return fileSize / 50 // 2%
|
||||||
|
default:
|
||||||
|
return fileSize / 100 // 1%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getContentType determines file MIME type
|
||||||
|
func getContentType(filename string) string {
|
||||||
|
ext := strings.ToLower(filepath.Ext(filename))
|
||||||
|
switch ext {
|
||||||
|
// Video formats
|
||||||
|
case ".mp4":
|
||||||
|
return "video/mp4"
|
||||||
|
case ".mkv":
|
||||||
|
return "video/x-matroska"
|
||||||
|
case ".avi":
|
||||||
|
return "video/x-msvideo"
|
||||||
|
case ".mov":
|
||||||
|
return "video/quicktime"
|
||||||
|
case ".wmv":
|
||||||
|
return "video/x-ms-wmv"
|
||||||
|
case ".webm":
|
||||||
|
return "video/webm"
|
||||||
|
case ".flv":
|
||||||
|
return "video/x-flv"
|
||||||
|
case ".m4v":
|
||||||
|
return "video/x-m4v"
|
||||||
|
case ".3gp":
|
||||||
|
return "video/3gpp"
|
||||||
|
case ".3g2":
|
||||||
|
return "video/3gpp2"
|
||||||
|
case ".ts":
|
||||||
|
return "video/mp2t"
|
||||||
|
case ".mts":
|
||||||
|
return "video/mp2t"
|
||||||
|
case ".m2ts":
|
||||||
|
return "video/mp2t"
|
||||||
|
case ".vob":
|
||||||
|
return "video/dvd"
|
||||||
|
case ".ogv":
|
||||||
|
return "video/ogg"
|
||||||
|
case ".asf":
|
||||||
|
return "video/x-ms-asf"
|
||||||
|
case ".rm":
|
||||||
|
return "application/vnd.rn-realmedia"
|
||||||
|
case ".rmvb":
|
||||||
|
return "application/vnd.rn-realmedia-vbr"
|
||||||
|
case ".f4v":
|
||||||
|
return "video/x-f4v"
|
||||||
|
case ".mpg", ".mpeg":
|
||||||
|
return "video/mpeg"
|
||||||
|
case ".m1v":
|
||||||
|
return "video/mpeg"
|
||||||
|
case ".m2v":
|
||||||
|
return "video/mpeg"
|
||||||
|
case ".divx":
|
||||||
|
return "video/divx"
|
||||||
|
case ".xvid":
|
||||||
|
return "video/x-msvideo"
|
||||||
|
// Audio formats
|
||||||
|
case ".mp3":
|
||||||
|
return "audio/mpeg"
|
||||||
|
case ".flac":
|
||||||
|
return "audio/flac"
|
||||||
|
case ".wav":
|
||||||
|
return "audio/wav"
|
||||||
|
case ".aac":
|
||||||
|
return "audio/aac"
|
||||||
|
case ".ogg":
|
||||||
|
return "audio/ogg"
|
||||||
|
case ".wma":
|
||||||
|
return "audio/x-ms-wma"
|
||||||
|
case ".m4a":
|
||||||
|
return "audio/mp4"
|
||||||
|
case ".opus":
|
||||||
|
return "audio/opus"
|
||||||
|
case ".ac3":
|
||||||
|
return "audio/ac3"
|
||||||
|
case ".dts":
|
||||||
|
return "audio/dts"
|
||||||
|
default:
|
||||||
|
return "application/octet-stream"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeTorrent(w http.ResponseWriter, r *http.Request) {
|
func removeTorrent(w http.ResponseWriter, r *http.Request) {
|
||||||
infoHash, ihOk := r.URL.Query()["infohash"]
|
query := r.URL.Query()
|
||||||
if !ihOk {
|
infoHashParams, ihOk := query["infohash"]
|
||||||
httpJSONError(w, "InfoHash is not provided", http.StatusNotFound)
|
|
||||||
|
if !ihOk || len(infoHashParams) == 0 {
|
||||||
|
httpJSONError(w, "InfoHash is not provided", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t := getTorrent(w, infoHash[0])
|
|
||||||
|
infoHash := infoHashParams[0]
|
||||||
|
t := getTorrent(w, infoHash)
|
||||||
if t == nil {
|
if t == nil {
|
||||||
httpJSONError(w, "Torrent not found", http.StatusNotFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Drop()
|
name := t.Name()
|
||||||
|
|
||||||
if os.RemoveAll(filepath.Join(torrentcliCfg.DataDir, t.Name())) != nil {
|
// Immediate response before cleanup
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte("Torrent removal initiated"))
|
||||||
|
|
||||||
httpJSONError(w, "ERROR WHEN REMOVING FILE", http.StatusInternalServerError)
|
// Asynchronous deletion after response
|
||||||
return
|
workerPool.Submit(func() {
|
||||||
|
t.Drop()
|
||||||
|
|
||||||
}
|
// Clean cache
|
||||||
|
torrentCache.Delete(infoHash)
|
||||||
|
|
||||||
|
// Remove files
|
||||||
|
if err := os.RemoveAll(filepath.Join(torrentcliCfg.DataDir, name)); err != nil {
|
||||||
|
log.Printf("[ERROR] Failed to remove torrent files [%s]: %s", name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[INFO] Successfully removed torrent: %s", name)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func listTorrents(w http.ResponseWriter, r *http.Request) {
|
func listTorrents(w http.ResponseWriter, r *http.Request) {
|
||||||
infoHash, ihOk := r.URL.Query()["infohash"]
|
query := r.URL.Query()
|
||||||
var ltRes listTorrentsRes
|
infoHashParams, ihOk := query["infohash"]
|
||||||
|
|
||||||
|
var ltRes listTorrentsRes
|
||||||
allTorrents := torrentCli.Torrents()
|
allTorrents := torrentCli.Torrents()
|
||||||
|
|
||||||
if ihOk {
|
if ihOk && len(infoHashParams) > 0 {
|
||||||
allTorrents = nil
|
t := getTorrent(w, infoHashParams[0])
|
||||||
t := getTorrent(w, infoHash[0])
|
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
allTorrents = append(allTorrents, t)
|
allTorrents = []*torrent.Torrent{t}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, t := range allTorrents {
|
// Parallel processing of statistics
|
||||||
ltRes.Torrents = append(ltRes.Torrents, parseTorrentStats(t))
|
type result struct {
|
||||||
|
stats torrentStatsRes
|
||||||
|
index int
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ihOk && len(ltRes.Torrents) < 1 {
|
results := make(chan result, len(allTorrents))
|
||||||
w.WriteHeader(404)
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
for i, t := range allTorrents {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(idx int, torrent *torrent.Torrent) {
|
||||||
|
defer wg.Done()
|
||||||
|
stats := parseTorrentStats(torrent)
|
||||||
|
results <- result{stats: stats, index: idx}
|
||||||
|
}(i, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(results)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Collect results in order
|
||||||
|
statsMap := make(map[int]torrentStatsRes)
|
||||||
|
for res := range results {
|
||||||
|
statsMap[res.index] = res.stats
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild list in order
|
||||||
|
ltRes.Torrents = make([]torrentStatsRes, 0, len(allTorrents))
|
||||||
|
for i := 0; i < len(allTorrents); i++ {
|
||||||
|
if stats, exists := statsMap[i]; exists {
|
||||||
|
ltRes.Torrents = append(ltRes.Torrents, stats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ihOk && len(ltRes.Torrents) == 0 {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
makeJSONResponse(w, <Res)
|
makeJSONResponse(w, <Res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddTorrent(w http.ResponseWriter, request *http.Request) {
|
func AddTorrent(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Limit upload file size
|
||||||
|
r.ParseMultipartForm(100 << 20) // 100MB max
|
||||||
|
|
||||||
a, _, error := request.FormFile("file")
|
file, _, err := r.FormFile("file")
|
||||||
if error != nil {
|
if err != nil {
|
||||||
log.Printf("error upload torrent")
|
log.Printf("[ERROR] Upload error: %s", err)
|
||||||
w.WriteHeader(http.StatusForbidden)
|
httpJSONError(w, "File upload error", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
metainf, err := metainfo.Load(file)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] MetaInfo load error: %s", err)
|
||||||
|
httpJSONError(w, "Invalid torrent file", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
metainf, er_ := metainfo.Load(a)
|
|
||||||
if er_ != nil {
|
|
||||||
log.Printf("error error when loading MetaInfo")
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
torrent, err := torrentCli.AddTorrent(metainf)
|
torrent, err := torrentCli.AddTorrent(metainf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err.Error())
|
log.Printf("[ERROR] Add torrent error: %s", err)
|
||||||
w.WriteHeader(http.StatusForbidden)
|
httpJSONError(w, "Failed to add torrent", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for information with timeout
|
||||||
|
if err := getInfoWithTimeout(torrent, 30*time.Second); err != nil {
|
||||||
|
log.Printf("[WARN] Timeout waiting for torrent info: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache metadata
|
||||||
|
cacheTorrentMetadata(torrent)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
w.Write([]byte(torrent.InfoHash().HexString()))
|
w.Write([]byte(torrent.InfoHash().HexString()))
|
||||||
|
|
||||||
}
|
}
|
||||||
func Init(w http.ResponseWriter, request *http.Request) {
|
|
||||||
|
|
||||||
w.Write([]byte("Torrent server is running"))
|
|
||||||
|
|
||||||
|
func Init(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
w.Write([]byte("Torrent server is running - Optimized version"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func playTorrent(w http.ResponseWriter, r *http.Request) {
|
func playTorrent(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ enum _AniSkipPhase { none, opening, ending }
|
||||||
bool _firstTime = true;
|
bool _firstTime = true;
|
||||||
|
|
||||||
class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin, WidgetsBindingObserver {
|
||||||
late final GlobalKey<VideoState> _key = GlobalKey<VideoState>();
|
late final GlobalKey<VideoState> _key = GlobalKey<VideoState>();
|
||||||
late final useLibass = ref.read(useLibassStateProvider);
|
late final useLibass = ref.read(useLibassStateProvider);
|
||||||
late final Player _player = Player(
|
late final Player _player = Player(
|
||||||
|
|
@ -340,23 +340,11 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
||||||
_completed;
|
_completed;
|
||||||
_currentTotalDurationSub;
|
_currentTotalDurationSub;
|
||||||
_loadAndroidFont().then((_) {
|
_loadAndroidFont().then((_) {
|
||||||
_player.open(
|
_openMedia(_video.value!, _streamController.geTCurrentPosition());
|
||||||
Media(
|
|
||||||
_video.value!.videoTrack!.id,
|
|
||||||
httpHeaders: _video.value!.headers,
|
|
||||||
start: _streamController.geTCurrentPosition(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (widget.isTorrent) {
|
if (widget.isTorrent) {
|
||||||
Future.delayed(const Duration(seconds: 10)).then((_) {
|
Future.delayed(const Duration(seconds: 10)).then((_) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_player.open(
|
_openMedia(_video.value!, _streamController.geTCurrentPosition());
|
||||||
Media(
|
|
||||||
_video.value!.videoTrack!.id,
|
|
||||||
httpHeaders: _video.value!.headers,
|
|
||||||
start: _streamController.geTCurrentPosition(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -365,6 +353,25 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
||||||
});
|
});
|
||||||
discordRpc.showChapterDetails(ref, widget.episode);
|
discordRpc.showChapterDetails(ref, widget.episode);
|
||||||
_currentPosition.addListener(_updateRpcTimestamp);
|
_currentPosition.addListener(_updateRpcTimestamp);
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.paused ||
|
||||||
|
state == AppLifecycleState.detached) {
|
||||||
|
_setCurrentPosition(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _openMedia(VideoPrefs prefs, [Duration? position]) {
|
||||||
|
return _player.open(
|
||||||
|
Media(
|
||||||
|
prefs.videoTrack!.id,
|
||||||
|
httpHeaders: prefs.headers,
|
||||||
|
start: position ?? _currentPosition.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadAndroidFont() async {
|
Future<void> _loadAndroidFont() async {
|
||||||
|
|
@ -413,17 +420,27 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_currentPosition.removeListener(_updateRpcTimestamp);
|
_currentPosition.removeListener(_updateRpcTimestamp);
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
_setCurrentPosition(true);
|
_setCurrentPosition(true);
|
||||||
_player.dispose();
|
_player.dispose();
|
||||||
_currentPositionSub.cancel();
|
_currentPositionSub.cancel();
|
||||||
_currentTotalDurationSub.cancel();
|
_currentTotalDurationSub.cancel();
|
||||||
_completed.cancel();
|
_completed.cancel();
|
||||||
|
_video.dispose();
|
||||||
|
_playbackSpeed.dispose();
|
||||||
|
_isDoubleSpeed.dispose();
|
||||||
|
_currentTotalDuration.dispose();
|
||||||
|
_showFitLabel.dispose();
|
||||||
|
_isCompleted.dispose();
|
||||||
|
_tempPosition.dispose();
|
||||||
|
_fit.dispose();
|
||||||
if (!_isDesktop) {
|
if (!_isDesktop) {
|
||||||
_setLandscapeMode(false);
|
_setLandscapeMode(false);
|
||||||
}
|
}
|
||||||
_skipPhase.dispose();
|
_skipPhase.dispose();
|
||||||
discordRpc.showIdleText();
|
discordRpc.showIdleText();
|
||||||
discordRpc.showOriginalTimestamp();
|
discordRpc.showOriginalTimestamp();
|
||||||
|
_currentPosition.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -508,27 +525,20 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage>
|
||||||
selected,
|
selected,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
if (_video.value?.videoTrack?.id == quality.videoTrack?.id) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
_video.value = quality;
|
_video.value = quality;
|
||||||
|
_player.stop();
|
||||||
if (quality.isLocal) {
|
if (quality.isLocal) {
|
||||||
if (widget.isLocal) {
|
if (widget.isLocal) {
|
||||||
_player.setVideoTrack(quality.videoTrack!);
|
_player.setVideoTrack(quality.videoTrack!);
|
||||||
} else {
|
} else {
|
||||||
_player.open(
|
_openMedia(quality);
|
||||||
Media(
|
|
||||||
quality.videoTrack!.id,
|
|
||||||
httpHeaders: quality.headers,
|
|
||||||
start: _currentPosition.value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_player.open(
|
_openMedia(quality);
|
||||||
Media(
|
|
||||||
quality.videoTrack!.id,
|
|
||||||
httpHeaders: quality.headers,
|
|
||||||
start: _currentPosition.value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_initSubtitleAndAudio = true;
|
_initSubtitleAndAudio = true;
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ class _DesktopControllerWidgetState
|
||||||
bool visible = true;
|
bool visible = true;
|
||||||
bool cursorVisible = true;
|
bool cursorVisible = true;
|
||||||
Duration controlsTransitionDuration = const Duration(milliseconds: 300);
|
Duration controlsTransitionDuration = const Duration(milliseconds: 300);
|
||||||
Color backdropColor = const Color(0x66000000);
|
// Color backdropColor = const Color(0x66000000);
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
|
|
||||||
int swipeDuration = 0; // Duration to seek in video
|
int swipeDuration = 0; // Duration to seek in video
|
||||||
|
|
@ -63,6 +63,7 @@ class _DesktopControllerWidgetState
|
||||||
|
|
||||||
final List<StreamSubscription> subscriptions = [];
|
final List<StreamSubscription> subscriptions = [];
|
||||||
DateTime last = DateTime.now();
|
DateTime last = DateTime.now();
|
||||||
|
Timer? _tapTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void setState(VoidCallback fn) {
|
void setState(VoidCallback fn) {
|
||||||
|
|
@ -98,6 +99,9 @@ class _DesktopControllerWidgetState
|
||||||
for (final subscription in subscriptions) {
|
for (final subscription in subscriptions) {
|
||||||
subscription.cancel();
|
subscription.cancel();
|
||||||
}
|
}
|
||||||
|
subscriptions.clear();
|
||||||
|
_timer?.cancel();
|
||||||
|
_tapTimer?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,8 +150,8 @@ class _DesktopControllerWidgetState
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
final bool modifyVolumeOnScroll = true;
|
final bool modifyVolumeOnScroll = true; // TODO. The variable is never changed
|
||||||
final bool toggleFullscreenOnDoublePress = true;
|
final bool toggleFullscreenOnDoublePress = true; // TODO. variable not changed
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CallbackShortcuts(
|
return CallbackShortcuts(
|
||||||
|
|
@ -250,6 +254,14 @@ class _DesktopControllerWidgetState
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
// use own timer with onTapUp instead of onDoubleTap.
|
||||||
|
// onDoubleTap uses 300ms which feels laggy when pausing
|
||||||
|
// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/constants.dart#L35
|
||||||
|
_tapTimer = Timer(const Duration(milliseconds: 100), () {
|
||||||
|
widget.videoController.player.playOrPause();
|
||||||
|
});
|
||||||
|
},
|
||||||
onLongPressStart: (e) {
|
onLongPressStart: (e) {
|
||||||
previousPlaybackSpeed =
|
previousPlaybackSpeed =
|
||||||
widget.videoController.player.state.rate;
|
widget.videoController.player.state.rate;
|
||||||
|
|
@ -274,6 +286,8 @@ class _DesktopControllerWidgetState
|
||||||
final difference = now.difference(last);
|
final difference = now.difference(last);
|
||||||
last = now;
|
last = now;
|
||||||
if (difference < const Duration(milliseconds: 400)) {
|
if (difference < const Duration(milliseconds: 400)) {
|
||||||
|
_tapTimer?.cancel();
|
||||||
|
_tapTimer = null;
|
||||||
final fullScreen = widget.desktopFullScreenPlayer;
|
final fullScreen = widget.desktopFullScreenPlayer;
|
||||||
await _changeFullScreen(ref, fullScreen);
|
await _changeFullScreen(ref, fullScreen);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ class _MobileControllerWidgetState
|
||||||
);
|
);
|
||||||
final ValueNotifier<double> _brightnessValue = ValueNotifier(0.0);
|
final ValueNotifier<double> _brightnessValue = ValueNotifier(0.0);
|
||||||
final ValueNotifier<bool> _brightnessIndicator = ValueNotifier(false);
|
final ValueNotifier<bool> _brightnessIndicator = ValueNotifier(false);
|
||||||
|
StreamSubscription<double>? _brightnessSubscription;
|
||||||
Timer? _brightnessTimer;
|
Timer? _brightnessTimer;
|
||||||
|
|
||||||
final ValueNotifier<double> _volumeValue = ValueNotifier(0.0);
|
final ValueNotifier<double> _volumeValue = ValueNotifier(0.0);
|
||||||
|
|
@ -127,6 +128,15 @@ class _MobileControllerWidgetState
|
||||||
for (final subscription in subscriptions) {
|
for (final subscription in subscriptions) {
|
||||||
subscription.cancel();
|
subscription.cancel();
|
||||||
}
|
}
|
||||||
|
_timer?.cancel();
|
||||||
|
_volumeTimer?.cancel();
|
||||||
|
_brightnessTimer?.cancel();
|
||||||
|
_volumeValue.dispose();
|
||||||
|
_volumeIndicator.dispose();
|
||||||
|
_brightnessValue.dispose();
|
||||||
|
_brightnessIndicator.dispose();
|
||||||
|
_brightnessSubscription?.cancel();
|
||||||
|
_volumeController.removeListener();
|
||||||
|
|
||||||
// package:screen_brightness
|
// package:screen_brightness
|
||||||
Future.microtask(() async {
|
Future.microtask(() async {
|
||||||
|
|
@ -134,7 +144,6 @@ class _MobileControllerWidgetState
|
||||||
await ScreenBrightness.instance.resetApplicationScreenBrightness();
|
await ScreenBrightness.instance.resetApplicationScreenBrightness();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
});
|
});
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,13 +249,14 @@ class _MobileControllerWidgetState
|
||||||
Future.microtask(() async {
|
Future.microtask(() async {
|
||||||
try {
|
try {
|
||||||
_brightnessValue.value = await ScreenBrightness.instance.application;
|
_brightnessValue.value = await ScreenBrightness.instance.application;
|
||||||
ScreenBrightness.instance.onApplicationScreenBrightnessChanged.listen((
|
_brightnessSubscription = ScreenBrightness
|
||||||
value,
|
.instance
|
||||||
) {
|
.onApplicationScreenBrightnessChanged
|
||||||
if (mounted) {
|
.listen((value) {
|
||||||
_brightnessValue.value = value;
|
if (mounted) {
|
||||||
}
|
_brightnessValue.value = value;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ class BrowseScreen extends ConsumerStatefulWidget {
|
||||||
|
|
||||||
class _BrowseScreenState extends ConsumerState<BrowseScreen>
|
class _BrowseScreenState extends ConsumerState<BrowseScreen>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
late final hideItems = ref.watch(hideItemsStateProvider);
|
late final hideItems = ref.read(hideItemsStateProvider);
|
||||||
|
final _textEditingController = TextEditingController();
|
||||||
late TabController _tabBarController;
|
late TabController _tabBarController;
|
||||||
|
|
||||||
late final _tabList = [
|
late final _tabList = [
|
||||||
|
|
@ -35,10 +36,9 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void initState() {
|
||||||
super.didChangeDependencies();
|
super.initState();
|
||||||
_tabBarController = TabController(length: _tabList.length, vsync: this);
|
_tabBarController = TabController(length: _tabList.length, vsync: this);
|
||||||
_tabBarController.animateTo(0);
|
|
||||||
_tabBarController.addListener(() {
|
_tabBarController.addListener(() {
|
||||||
_chekPermission();
|
_chekPermission();
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -52,7 +52,13 @@ class _BrowseScreenState extends ConsumerState<BrowseScreen>
|
||||||
await StorageProvider().requestPermission();
|
await StorageProvider().requestPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
final _textEditingController = TextEditingController();
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tabBarController.dispose();
|
||||||
|
_textEditingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
bool _isSearch = false;
|
bool _isSearch = false;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:json_view/json_view.dart';
|
import 'package:json_view/json_view.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
@ -59,13 +61,14 @@ class _CodeEditorPageState extends ConsumerState<CodeEditorPage> {
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
late final _logStreamController = Logger.logStreamController;
|
late final _logStreamController = Logger.logStreamController;
|
||||||
|
late final StreamSubscription _logSubscription;
|
||||||
final _scrollController = ScrollController();
|
final _scrollController = ScrollController();
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controller.text = source?.sourceCode ?? "";
|
_controller.text = source?.sourceCode ?? "";
|
||||||
useLogger = true;
|
useLogger = true;
|
||||||
_logStreamController.stream.asBroadcastStream().listen((event) async {
|
_logSubscription = _logStreamController.stream.listen((event) async {
|
||||||
_logsNotifier.value.add(event);
|
_logsNotifier.value.add(event);
|
||||||
try {
|
try {
|
||||||
await Future.delayed(const Duration(milliseconds: 5));
|
await Future.delayed(const Duration(milliseconds: 5));
|
||||||
|
|
@ -133,11 +136,12 @@ class _CodeEditorPageState extends ConsumerState<CodeEditorPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
_logSubscription.cancel();
|
||||||
_logsNotifier.value.clear();
|
_logsNotifier.value.clear();
|
||||||
_scrollController.dispose();
|
_scrollController.dispose();
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
useLogger = false;
|
useLogger = false;
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,12 @@ class _ExtensionScreenState extends ConsumerState<ExtensionScreen> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _updateSource(Source source) {
|
Future<void> _updateSource(Source source) {
|
||||||
return ref.read(
|
return ref.read(
|
||||||
fetchItemSourcesListProvider(
|
fetchItemSourcesListProvider(
|
||||||
|
|
|
||||||
|
|
@ -298,6 +298,13 @@ class EditTextDialogWidget extends StatefulWidget {
|
||||||
|
|
||||||
class _EditTextDialogWidgetState extends State<EditTextDialogWidget> {
|
class _EditTextDialogWidgetState extends State<EditTextDialogWidget> {
|
||||||
late final _controller = TextEditingController(text: widget.text);
|
late final _controller = TextEditingController(text: widget.text);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ class _GlobalSearchScreenState extends ConsumerState<GlobalSearchScreen> {
|
||||||
String query = "";
|
String query = "";
|
||||||
final _textEditingController = TextEditingController();
|
final _textEditingController = TextEditingController();
|
||||||
late final List<Source> sourceList =
|
late final List<Source> sourceList =
|
||||||
ref.watch(onlyIncludePinnedSourceStateProvider)
|
ref.read(onlyIncludePinnedSourceStateProvider)
|
||||||
? isar.sources
|
? isar.sources
|
||||||
.filter()
|
.filter()
|
||||||
.isPinnedEqualTo(true)
|
.isPinnedEqualTo(true)
|
||||||
|
|
@ -97,6 +97,12 @@ class _GlobalSearchScreenState extends ConsumerState<GlobalSearchScreen> {
|
||||||
: Container(),
|
: Container(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_textEditingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SourceSearchScreen extends StatefulWidget {
|
class SourceSearchScreen extends StatefulWidget {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,13 @@ class SourcesScreen extends ConsumerStatefulWidget {
|
||||||
|
|
||||||
class _SourcesScreenState extends ConsumerState<SourcesScreen> {
|
class _SourcesScreenState extends ConsumerState<SourcesScreen> {
|
||||||
final controller = ScrollController();
|
final controller = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = l10nLocalizations(context)!;
|
final l10n = l10nLocalizations(context)!;
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ class HistoryScreen extends ConsumerStatefulWidget {
|
||||||
|
|
||||||
class _HistoryScreenState extends ConsumerState<HistoryScreen>
|
class _HistoryScreenState extends ConsumerState<HistoryScreen>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
|
final _textEditingController = TextEditingController();
|
||||||
late TabController _tabBarController;
|
late TabController _tabBarController;
|
||||||
int tabs = 3;
|
|
||||||
|
|
||||||
void tabListener() {
|
void tabListener() {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -46,171 +46,150 @@ class _HistoryScreenState extends ConsumerState<HistoryScreen>
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tabBarController = TabController(length: tabs, vsync: this);
|
final hideItems = ref.read(hideItemsStateProvider);
|
||||||
_tabBarController.animateTo(0);
|
final tabCount = [
|
||||||
|
if (!hideItems.contains("/MangaLibrary")) "/MangaLibrary",
|
||||||
|
if (!hideItems.contains("/AnimeLibrary")) "/AnimeLibrary",
|
||||||
|
if (!hideItems.contains("/NovelLibrary")) "/NovelLibrary",
|
||||||
|
].length;
|
||||||
|
_tabBarController = TabController(length: tabCount, vsync: this);
|
||||||
_tabBarController.addListener(tabListener);
|
_tabBarController.addListener(tabListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
final _textEditingController = TextEditingController();
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tabBarController.dispose();
|
||||||
|
_textEditingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
bool _isSearch = false;
|
bool _isSearch = false;
|
||||||
List<History> entriesData = [];
|
// List<History> _entriesData = []; // TODO. The variable is never used/modified
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
int newTabs = 0;
|
|
||||||
final hideItems = ref.watch(hideItemsStateProvider);
|
final hideItems = ref.watch(hideItemsStateProvider);
|
||||||
|
|
||||||
if (!hideItems.contains("/MangaLibrary")) newTabs++;
|
|
||||||
if (!hideItems.contains("/AnimeLibrary")) newTabs++;
|
|
||||||
if (!hideItems.contains("/NovelLibrary")) newTabs++;
|
|
||||||
if (newTabs == 0) {
|
|
||||||
return SizedBox.shrink();
|
|
||||||
}
|
|
||||||
if (tabs != newTabs) {
|
|
||||||
_tabBarController.removeListener(tabListener);
|
|
||||||
_tabBarController.dispose();
|
|
||||||
_tabBarController = TabController(length: newTabs, vsync: this);
|
|
||||||
_tabBarController.animateTo(0);
|
|
||||||
_tabBarController.addListener(tabListener);
|
|
||||||
setState(() {
|
|
||||||
tabs = newTabs;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
final l10n = l10nLocalizations(context)!;
|
final l10n = l10nLocalizations(context)!;
|
||||||
return DefaultTabController(
|
return Scaffold(
|
||||||
animationDuration: Duration.zero,
|
appBar: AppBar(
|
||||||
length: newTabs,
|
elevation: 0,
|
||||||
child: Scaffold(
|
backgroundColor: Colors.transparent,
|
||||||
appBar: AppBar(
|
title: _isSearch
|
||||||
elevation: 0,
|
? null
|
||||||
backgroundColor: Colors.transparent,
|
: Text(
|
||||||
title: _isSearch
|
l10n.history,
|
||||||
? null
|
style: TextStyle(color: Theme.of(context).hintColor),
|
||||||
: Text(
|
|
||||||
l10n.history,
|
|
||||||
style: TextStyle(color: Theme.of(context).hintColor),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
_isSearch
|
|
||||||
? SeachFormTextField(
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
onSuffixPressed: () {
|
|
||||||
_textEditingController.clear();
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_isSearch = false;
|
|
||||||
});
|
|
||||||
_textEditingController.clear();
|
|
||||||
},
|
|
||||||
controller: _textEditingController,
|
|
||||||
)
|
|
||||||
: IconButton(
|
|
||||||
splashRadius: 20,
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_isSearch = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
Icons.search,
|
|
||||||
color: Theme.of(context).hintColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
splashRadius: 20,
|
|
||||||
onPressed: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text(l10n.remove_everything),
|
|
||||||
content: Text(l10n.remove_everything_msg),
|
|
||||||
actions: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
child: Text(l10n.cancel),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 15),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => clearHistory(hideItems),
|
|
||||||
child: Text(l10n.ok),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
Icons.delete_sweep_outlined,
|
|
||||||
color: Theme.of(context).hintColor,
|
|
||||||
),
|
),
|
||||||
|
actions: [
|
||||||
|
_isSearch
|
||||||
|
? SeachFormTextField(
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
onSuffixPressed: () {
|
||||||
|
_textEditingController.clear();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_isSearch = false;
|
||||||
|
});
|
||||||
|
_textEditingController.clear();
|
||||||
|
},
|
||||||
|
controller: _textEditingController,
|
||||||
|
)
|
||||||
|
: IconButton(
|
||||||
|
splashRadius: 20,
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_isSearch = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.search, color: Theme.of(context).hintColor),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
splashRadius: 20,
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(l10n.remove_everything),
|
||||||
|
content: Text(l10n.remove_everything_msg),
|
||||||
|
actions: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Text(l10n.cancel),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (mounted) Navigator.pop(context);
|
||||||
|
await _clearHistory(hideItems);
|
||||||
|
},
|
||||||
|
child: Text(l10n.ok),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.delete_sweep_outlined,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
bottom: TabBar(
|
||||||
|
indicatorSize: TabBarIndicatorSize.tab,
|
||||||
|
controller: _tabBarController,
|
||||||
|
tabs: [
|
||||||
|
if (!hideItems.contains("/MangaLibrary")) Tab(text: l10n.manga),
|
||||||
|
if (!hideItems.contains("/AnimeLibrary")) Tab(text: l10n.anime),
|
||||||
|
if (!hideItems.contains("/NovelLibrary")) Tab(text: l10n.novel),
|
||||||
],
|
],
|
||||||
bottom: TabBar(
|
|
||||||
indicatorSize: TabBarIndicatorSize.tab,
|
|
||||||
controller: _tabBarController,
|
|
||||||
tabs: [
|
|
||||||
if (!hideItems.contains("/MangaLibrary")) Tab(text: l10n.manga),
|
|
||||||
if (!hideItems.contains("/AnimeLibrary")) Tab(text: l10n.anime),
|
|
||||||
if (!hideItems.contains("/NovelLibrary")) Tab(text: l10n.novel),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 10),
|
|
||||||
child: TabBarView(
|
|
||||||
controller: _tabBarController,
|
|
||||||
children: [
|
|
||||||
if (!hideItems.contains("/MangaLibrary"))
|
|
||||||
HistoryTab(
|
|
||||||
itemType: ItemType.manga,
|
|
||||||
query: _textEditingController.text,
|
|
||||||
),
|
|
||||||
if (!hideItems.contains("/AnimeLibrary"))
|
|
||||||
HistoryTab(
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
query: _textEditingController.text,
|
|
||||||
),
|
|
||||||
if (!hideItems.contains("/NovelLibrary"))
|
|
||||||
HistoryTab(
|
|
||||||
itemType: ItemType.novel,
|
|
||||||
query: _textEditingController.text,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
body: TabBarView(
|
||||||
|
controller: _tabBarController,
|
||||||
|
children: [
|
||||||
|
if (!hideItems.contains("/MangaLibrary"))
|
||||||
|
HistoryTab(
|
||||||
|
itemType: ItemType.manga,
|
||||||
|
query: _textEditingController.text,
|
||||||
|
),
|
||||||
|
if (!hideItems.contains("/AnimeLibrary"))
|
||||||
|
HistoryTab(
|
||||||
|
itemType: ItemType.anime,
|
||||||
|
query: _textEditingController.text,
|
||||||
|
),
|
||||||
|
if (!hideItems.contains("/NovelLibrary"))
|
||||||
|
HistoryTab(
|
||||||
|
itemType: ItemType.novel,
|
||||||
|
query: _textEditingController.text,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearHistory(List<String> hideItems) {
|
Future<void> _clearHistory(List<String> hideItems) async {
|
||||||
List<History> histories = isar.historys
|
List<History> histories = await isar.historys
|
||||||
.filter()
|
.filter()
|
||||||
.idIsNotNull()
|
.idIsNotNull()
|
||||||
.chapter(
|
.chapter(
|
||||||
(q) =>
|
(q) =>
|
||||||
q.manga((q) => q.itemTypeEqualTo(getCurrentItemType(hideItems))),
|
q.manga((q) => q.itemTypeEqualTo(getCurrentItemType(hideItems))),
|
||||||
)
|
)
|
||||||
.findAllSync()
|
.findAll();
|
||||||
.toList();
|
final List<Id> idsToDelete = histories.map((h) => h.id!).toList();
|
||||||
isar.writeTxnSync(() {
|
await isar.writeTxn(() => isar.historys.deleteAll(idsToDelete));
|
||||||
for (var history in histories) {
|
|
||||||
isar.historys.deleteSync(history.id!);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (mounted) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemType getCurrentItemType(List<String> hideItems) {
|
ItemType getCurrentItemType(List<String> hideItems) {
|
||||||
|
|
@ -233,9 +212,13 @@ class HistoryTab extends ConsumerStatefulWidget {
|
||||||
ConsumerState<HistoryTab> createState() => _HistoryTabState();
|
ConsumerState<HistoryTab> createState() => _HistoryTabState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HistoryTabState extends ConsumerState<HistoryTab> {
|
class _HistoryTabState extends ConsumerState<HistoryTab>
|
||||||
|
with AutomaticKeepAliveClientMixin {
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
final l10n = l10nLocalizations(context)!;
|
final l10n = l10nLocalizations(context)!;
|
||||||
final history = ref.watch(
|
final history = ref.watch(
|
||||||
getAllHistoryStreamProvider(
|
getAllHistoryStreamProvider(
|
||||||
|
|
@ -243,182 +226,179 @@ class _HistoryTabState extends ConsumerState<HistoryTab> {
|
||||||
search: widget.query,
|
search: widget.query,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return Scaffold(
|
return history.when(
|
||||||
body: history.when(
|
data: (entries) {
|
||||||
data: (entries) {
|
if (entries.isNotEmpty) {
|
||||||
if (entries.isNotEmpty) {
|
return CustomScrollView(
|
||||||
return CustomScrollView(
|
slivers: [
|
||||||
slivers: [
|
CustomSliverGroupedListView<History, String>(
|
||||||
CustomSliverGroupedListView<History, String>(
|
elements: entries,
|
||||||
elements: entries,
|
groupBy: (element) => dateFormat(
|
||||||
groupBy: (element) => dateFormat(
|
element.date!,
|
||||||
element.date!,
|
context: context,
|
||||||
context: context,
|
ref: ref,
|
||||||
ref: ref,
|
forHistoryValue: true,
|
||||||
forHistoryValue: true,
|
useRelativeTimesTamps: false,
|
||||||
useRelativeTimesTamps: false,
|
),
|
||||||
),
|
groupSeparatorBuilder: (String groupByValue) => Padding(
|
||||||
groupSeparatorBuilder: (String groupByValue) => Padding(
|
padding: const EdgeInsets.only(bottom: 8, left: 12),
|
||||||
padding: const EdgeInsets.only(bottom: 8, left: 12),
|
child: Row(
|
||||||
child: Row(
|
children: [
|
||||||
children: [
|
Text(
|
||||||
Text(
|
dateFormat(
|
||||||
dateFormat(
|
null,
|
||||||
null,
|
context: context,
|
||||||
context: context,
|
stringDate: groupByValue,
|
||||||
stringDate: groupByValue,
|
ref: ref,
|
||||||
ref: ref,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
itemBuilder: (context, History element) {
|
|
||||||
final manga = element.chapter.value!.manga.value!;
|
|
||||||
final chapter = element.chapter.value!;
|
|
||||||
return ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
padding: const EdgeInsets.all(0),
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(0),
|
|
||||||
),
|
|
||||||
elevation: 0,
|
|
||||||
shadowColor: Colors.transparent,
|
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
],
|
||||||
await chapter.pushToReaderView(context);
|
),
|
||||||
},
|
),
|
||||||
child: Padding(
|
itemBuilder: (context, History element) {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
final chapter = element.chapter.value!;
|
||||||
child: SizedBox(
|
final manga = chapter.manga.value!;
|
||||||
height: 105,
|
return ElevatedButton(
|
||||||
child: Row(
|
style: ElevatedButton.styleFrom(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
padding: const EdgeInsets.all(0),
|
||||||
children: [
|
backgroundColor: Colors.transparent,
|
||||||
SizedBox(
|
shape: RoundedRectangleBorder(
|
||||||
width: 60,
|
borderRadius: BorderRadius.circular(0),
|
||||||
height: 90,
|
),
|
||||||
child: ElevatedButton(
|
elevation: 0,
|
||||||
style: ElevatedButton.styleFrom(
|
shadowColor: Colors.transparent,
|
||||||
padding: const EdgeInsets.all(0),
|
),
|
||||||
shape: RoundedRectangleBorder(
|
onPressed: () async {
|
||||||
borderRadius: BorderRadius.circular(7),
|
await chapter.pushToReaderView(context);
|
||||||
),
|
},
|
||||||
),
|
child: Padding(
|
||||||
onPressed: () {
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
context.push(
|
child: SizedBox(
|
||||||
'/manga-reader/detail',
|
height: 105,
|
||||||
extra: manga.id,
|
child: Row(
|
||||||
);
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
},
|
children: [
|
||||||
child: ClipRRect(
|
SizedBox(
|
||||||
|
width: 60,
|
||||||
|
height: 90,
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(7),
|
borderRadius: BorderRadius.circular(7),
|
||||||
child: getCoverImage(manga),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
onPressed: () {
|
||||||
|
context.push(
|
||||||
|
'/manga-reader/detail',
|
||||||
|
extra: manga.id,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(7),
|
||||||
|
child: _getCoverImage(manga),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Flexible(
|
),
|
||||||
child: Row(
|
Flexible(
|
||||||
children: [
|
child: Row(
|
||||||
Expanded(
|
children: [
|
||||||
child: Container(
|
Expanded(
|
||||||
color: Colors.transparent,
|
child: Container(
|
||||||
child: Padding(
|
color: Colors.transparent,
|
||||||
padding: const EdgeInsets.all(8.0),
|
child: Padding(
|
||||||
child: Column(
|
padding: const EdgeInsets.all(8.0),
|
||||||
mainAxisAlignment:
|
child: Column(
|
||||||
MainAxisAlignment.center,
|
mainAxisAlignment:
|
||||||
crossAxisAlignment:
|
MainAxisAlignment.center,
|
||||||
CrossAxisAlignment.start,
|
crossAxisAlignment:
|
||||||
children: [
|
CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
manga.name!,
|
Text(
|
||||||
style: TextStyle(
|
manga.name!,
|
||||||
fontSize: 14,
|
style: TextStyle(
|
||||||
color: Theme.of(
|
fontSize: 14,
|
||||||
context,
|
color: Theme.of(
|
||||||
).textTheme.bodyLarge!.color,
|
context,
|
||||||
fontWeight: FontWeight.bold,
|
).textTheme.bodyLarge!.color,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
),
|
||||||
|
Wrap(
|
||||||
|
crossAxisAlignment:
|
||||||
|
WrapCrossAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
chapter.name!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyLarge!
|
||||||
|
.color,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.start,
|
Text(
|
||||||
),
|
" - ${dateFormatHour(element.date!, context)}",
|
||||||
Wrap(
|
style: TextStyle(
|
||||||
crossAxisAlignment:
|
fontSize: 11,
|
||||||
WrapCrossAlignment.end,
|
color: Theme.of(context)
|
||||||
children: [
|
.textTheme
|
||||||
Text(
|
.bodyLarge!
|
||||||
chapter.name!,
|
.color,
|
||||||
style: TextStyle(
|
fontWeight: FontWeight.w400,
|
||||||
fontSize: 11,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyLarge!
|
|
||||||
.color,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Text(
|
),
|
||||||
" - ${dateFormatHour(element.date!, context)}",
|
],
|
||||||
style: TextStyle(
|
),
|
||||||
fontSize: 11,
|
],
|
||||||
color: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyLarge!
|
|
||||||
.color,
|
|
||||||
fontWeight:
|
|
||||||
FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
),
|
||||||
onPressed: () => openDeleteDialog(
|
IconButton(
|
||||||
l10n,
|
onPressed: () => _openDeleteDialog(
|
||||||
manga,
|
l10n,
|
||||||
element.id,
|
manga,
|
||||||
),
|
element.id,
|
||||||
icon: Icon(
|
|
||||||
Icons.delete_outline,
|
|
||||||
size: 25,
|
|
||||||
color: Theme.of(
|
|
||||||
context,
|
|
||||||
).textTheme.bodyLarge!.color,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
icon: Icon(
|
||||||
),
|
Icons.delete_outline,
|
||||||
|
size: 25,
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.bodyLarge!.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
itemComparator: (item1, item2) =>
|
},
|
||||||
item1.date!.compareTo(item2.date!),
|
itemComparator: (item1, item2) =>
|
||||||
order: GroupedListOrder.DESC,
|
item1.date!.compareTo(item2.date!),
|
||||||
),
|
order: GroupedListOrder.DESC,
|
||||||
],
|
),
|
||||||
);
|
],
|
||||||
}
|
);
|
||||||
return Center(child: Text(l10n.nothing_read_recently));
|
}
|
||||||
},
|
return Center(child: Text(l10n.nothing_read_recently));
|
||||||
error: (Object error, StackTrace stackTrace) {
|
},
|
||||||
return ErrorText(error);
|
error: (Object error, StackTrace stackTrace) {
|
||||||
},
|
return ErrorText(error);
|
||||||
loading: () {
|
},
|
||||||
return const ProgressCenter();
|
loading: () {
|
||||||
},
|
return const ProgressCenter();
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget getCoverImage(Manga manga) {
|
Widget _getCoverImage(Manga manga) {
|
||||||
return manga.customCoverImage != null
|
return manga.customCoverImage != null
|
||||||
? Image.memory(manga.customCoverImage as Uint8List)
|
? Image.memory(manga.customCoverImage as Uint8List)
|
||||||
: cachedCompressedNetworkImage(
|
: cachedCompressedNetworkImage(
|
||||||
|
|
@ -434,7 +414,7 @@ class _HistoryTabState extends ConsumerState<HistoryTab> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void openDeleteDialog(AppLocalizations l10n, Manga manga, int? deleteId) {
|
void _openDeleteDialog(AppLocalizations l10n, Manga manga, int? deleteId) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,13 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_textEditingController.dispose();
|
||||||
|
tabBarController?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _updateLibrary(List<Manga> mangaList) async {
|
Future<void> _updateLibrary(List<Manga> mangaList) async {
|
||||||
bool isDark = ref.read(themeModeStateProvider);
|
bool isDark = ref.read(themeModeStateProvider);
|
||||||
botToast(
|
botToast(
|
||||||
|
|
@ -2078,6 +2085,11 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen>
|
||||||
|
|
||||||
void _importLocal(BuildContext context, ItemType itemType) {
|
void _importLocal(BuildContext context, ItemType itemType) {
|
||||||
final l10n = l10nLocalizations(context)!;
|
final l10n = l10nLocalizations(context)!;
|
||||||
|
final filesText = switch (itemType) {
|
||||||
|
ItemType.manga => ".zip, .cbz",
|
||||||
|
ItemType.anime => ".mp4, .mkv, .avi, and more",
|
||||||
|
ItemType.novel => ".epub",
|
||||||
|
};
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -2126,7 +2138,7 @@ void _importLocal(BuildContext context, ItemType itemType) {
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.archive_outlined),
|
const Icon(Icons.archive_outlined),
|
||||||
Text(
|
Text(
|
||||||
"${l10n.import_files} ( ${itemType == ItemType.manga ? ".zip, .cbz" : ".mp4, .mkv, .avi, and more"} )",
|
"${l10n.import_files} ( $filesText )",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
context,
|
context,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'package:epubx/epubx.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:mangayomi/main.dart';
|
import 'package:mangayomi/main.dart';
|
||||||
import 'package:mangayomi/models/chapter.dart';
|
import 'package:mangayomi/models/chapter.dart';
|
||||||
|
|
@ -19,9 +21,11 @@ Future importArchivesFromFile(
|
||||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||||
allowMultiple: true,
|
allowMultiple: true,
|
||||||
type: FileType.custom,
|
type: FileType.custom,
|
||||||
allowedExtensions: itemType == ItemType.manga
|
allowedExtensions: switch (itemType) {
|
||||||
? ['cbz', 'zip']
|
ItemType.manga => ['cbz', 'zip'],
|
||||||
: ['mp4', 'mov', 'avi', 'flv', 'wmv', 'mpeg', 'mkv'],
|
ItemType.anime => ['mp4', 'mov', 'avi', 'flv', 'wmv', 'mpeg', 'mkv'],
|
||||||
|
ItemType.novel => ['epub'],
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
final dateNow = DateTime.now().millisecondsSinceEpoch;
|
final dateNow = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
@ -57,16 +61,45 @@ Future importArchivesFromFile(
|
||||||
manga.customCoverImage = itemType == ItemType.manga ? data!.$3 : null;
|
manga.customCoverImage = itemType == ItemType.manga ? data!.$3 : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
isar.writeTxnSync(() {
|
await isar.writeTxn(() async {
|
||||||
isar.mangas.putSync(manga);
|
final mangaId = await isar.mangas.put(manga);
|
||||||
final chapters = Chapter(
|
final List<Chapter> chapters = [];
|
||||||
name: itemType == ItemType.manga ? data!.$1 : name,
|
if (itemType == ItemType.novel) {
|
||||||
archivePath: itemType == ItemType.manga ? data!.$4 : file.path,
|
final bytes = await File(file.path!).readAsBytes();
|
||||||
mangaId: manga.id,
|
final book = await EpubReader.readBook(bytes);
|
||||||
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
if (book.Content != null && book.Content!.Images != null) {
|
||||||
)..manga.value = manga;
|
final coverImage =
|
||||||
isar.chapters.putSync(chapters);
|
book.Content!.Images!.containsKey("media/file0.png")
|
||||||
chapters.manga.saveSync();
|
? book.Content!.Images!["media/file0.png"]!.Content
|
||||||
|
: book.Content!.Images!.values.first.Content;
|
||||||
|
await isar.mangas.put(manga..customCoverImage = coverImage);
|
||||||
|
}
|
||||||
|
for (var chapter in book.Chapters ?? []) {
|
||||||
|
chapters.add(
|
||||||
|
Chapter(
|
||||||
|
mangaId: mangaId,
|
||||||
|
name: chapter.Title is String && chapter.Title.isEmpty
|
||||||
|
? "Book"
|
||||||
|
: chapter.Title,
|
||||||
|
archivePath: file.path,
|
||||||
|
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
||||||
|
)..manga.value = manga,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chapters.add(
|
||||||
|
Chapter(
|
||||||
|
name: itemType == ItemType.manga ? data!.$1 : name,
|
||||||
|
archivePath: itemType == ItemType.manga ? data!.$4 : file.path,
|
||||||
|
mangaId: manga.id,
|
||||||
|
updatedAt: DateTime.now().millisecondsSinceEpoch,
|
||||||
|
)..manga.value = manga,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (final chapter in chapters) {
|
||||||
|
await isar.chapters.put(chapter);
|
||||||
|
await chapter.manga.save();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +113,7 @@ String _getName(String path) {
|
||||||
.split("\\")
|
.split("\\")
|
||||||
.last
|
.last
|
||||||
.replaceAll(
|
.replaceAll(
|
||||||
RegExp(r'\.(mp4|mov|avi|flv|wmv|mpeg|mkv|cbz|zip|cbt|tar)'),
|
RegExp(r'\.(mp4|mov|avi|flv|wmv|mpeg|mkv|cbz|zip|cbt|tar|epub)'),
|
||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ part of 'local_archive.dart';
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$importArchivesFromFileHash() =>
|
String _$importArchivesFromFileHash() =>
|
||||||
r'e57fafc17833a24bccdd8f945a4c8e6dc50b49c0';
|
r'4d92aaade0544f76214030364433f91d27570b5a';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|
|
||||||
|
|
@ -126,34 +126,10 @@ class _LibraryGridViewWidgetState extends State<LibraryGridViewWidget> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
if (!isLongPressed) {
|
_handleLongOrSecondaryTap(isLongPressed, ref, entry);
|
||||||
ref
|
|
||||||
.read(mangasListStateProvider.notifier)
|
|
||||||
.update(entry);
|
|
||||||
|
|
||||||
ref
|
|
||||||
.read(isLongPressedMangaStateProvider.notifier)
|
|
||||||
.update(!isLongPressed);
|
|
||||||
} else {
|
|
||||||
ref
|
|
||||||
.read(mangasListStateProvider.notifier)
|
|
||||||
.update(entry);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onSecondaryTap: () {
|
onSecondaryTap: () {
|
||||||
if (!isLongPressed) {
|
_handleLongOrSecondaryTap(isLongPressed, ref, entry);
|
||||||
ref
|
|
||||||
.read(mangasListStateProvider.notifier)
|
|
||||||
.update(entry);
|
|
||||||
|
|
||||||
ref
|
|
||||||
.read(isLongPressedMangaStateProvider.notifier)
|
|
||||||
.update(!isLongPressed);
|
|
||||||
} else {
|
|
||||||
ref
|
|
||||||
.read(mangasListStateProvider.notifier)
|
|
||||||
.update(entry);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
Stack(
|
Stack(
|
||||||
|
|
@ -446,4 +422,17 @@ class _LibraryGridViewWidgetState extends State<LibraryGridViewWidget> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleLongOrSecondaryTap(
|
||||||
|
bool isLongPressed,
|
||||||
|
WidgetRef ref,
|
||||||
|
Manga entry,
|
||||||
|
) {
|
||||||
|
if (!isLongPressed) {
|
||||||
|
ref.read(mangasListStateProvider.notifier).update(entry);
|
||||||
|
ref.read(isLongPressedMangaStateProvider.notifier).update(!isLongPressed);
|
||||||
|
} else {
|
||||||
|
ref.read(mangasListStateProvider.notifier).update(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ class SeachFormTextField extends StatelessWidget {
|
||||||
final VoidCallback onSuffixPressed;
|
final VoidCallback onSuffixPressed;
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final Function(String)? onFieldSubmitted;
|
final Function(String)? onFieldSubmitted;
|
||||||
|
final bool autofocus;
|
||||||
const SeachFormTextField({
|
const SeachFormTextField({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
|
|
@ -14,6 +15,7 @@ class SeachFormTextField extends StatelessWidget {
|
||||||
required this.controller,
|
required this.controller,
|
||||||
this.onFieldSubmitted,
|
this.onFieldSubmitted,
|
||||||
required this.onSuffixPressed,
|
required this.onSuffixPressed,
|
||||||
|
this.autofocus = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -21,7 +23,7 @@ class SeachFormTextField extends StatelessWidget {
|
||||||
final l10n = l10nLocalizations(context)!;
|
final l10n = l10nLocalizations(context)!;
|
||||||
return Flexible(
|
return Flexible(
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
autofocus: true,
|
autofocus: autofocus,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
keyboardType: TextInputType.text,
|
keyboardType: TextInputType.text,
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
|
|
|
||||||
|
|
@ -89,9 +89,15 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
final offetProvider = StateProvider((ref) => 0.0);
|
final offetProvider = StateProvider((ref) => 0.0);
|
||||||
bool _expanded = false;
|
bool _expanded = false;
|
||||||
ScrollController _scrollController = ScrollController();
|
late final ScrollController _scrollController;
|
||||||
late final isLocalArchive = widget.manga!.isLocalArchive ?? false;
|
late final isLocalArchive = widget.manga!.isLocalArchive ?? false;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
||||||
|
|
@ -24,113 +24,116 @@ class ChapterListTileWidget extends ConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final isLongPressed = ref.watch(isLongPressedStateProvider);
|
|
||||||
final l10n = l10nLocalizations(context)!;
|
final l10n = l10nLocalizations(context)!;
|
||||||
return Container(
|
return Container(
|
||||||
color: chapterList.contains(chapter)
|
color: chapterList.contains(chapter)
|
||||||
? context.primaryColor.withValues(alpha: 0.4)
|
? context.primaryColor.withValues(alpha: 0.4)
|
||||||
: null,
|
: null,
|
||||||
child: ListTile(
|
child: GestureDetector(
|
||||||
textColor: chapter.isRead!
|
onLongPress: () => _handleInteraction(ref),
|
||||||
? context.isLight
|
onSecondaryTap: () => _handleInteraction(ref),
|
||||||
? Colors.black.withValues(alpha: 0.4)
|
child: ListTile(
|
||||||
: Colors.white.withValues(alpha: 0.3)
|
textColor: chapter.isRead!
|
||||||
: null,
|
? context.isLight
|
||||||
selectedColor: chapter.isRead!
|
? Colors.black.withValues(alpha: 0.4)
|
||||||
? Colors.white.withValues(alpha: 0.3)
|
: Colors.white.withValues(alpha: 0.3)
|
||||||
: Colors.white,
|
: null,
|
||||||
onLongPress: () {
|
selectedColor: chapter.isRead!
|
||||||
if (!isLongPressed) {
|
? Colors.white.withValues(alpha: 0.3)
|
||||||
ref.read(chaptersListStateProvider.notifier).update(chapter);
|
: Colors.white,
|
||||||
|
onTap: () async => _handleInteraction(ref, context),
|
||||||
ref
|
title: Row(
|
||||||
.read(isLongPressedStateProvider.notifier)
|
children: [
|
||||||
.update(!isLongPressed);
|
chapter.isBookmarked!
|
||||||
} else {
|
? Icon(Icons.bookmark, size: 16, color: context.primaryColor)
|
||||||
ref.read(chaptersListStateProvider.notifier).update(chapter);
|
: Container(),
|
||||||
}
|
Flexible(child: _buildTitle(chapter.name!, context)),
|
||||||
},
|
],
|
||||||
onTap: () async {
|
),
|
||||||
if (isLongPressed) {
|
subtitle: Row(
|
||||||
ref.read(chaptersListStateProvider.notifier).update(chapter);
|
children: [
|
||||||
} else {
|
if ((chapter.manga.value!.isLocalArchive ?? false) == false)
|
||||||
chapter.pushToReaderView(context, ignoreIsRead: true);
|
Text(
|
||||||
}
|
chapter.dateUpload == null || chapter.dateUpload!.isEmpty
|
||||||
},
|
? ""
|
||||||
title: Row(
|
: dateFormat(
|
||||||
children: [
|
chapter.dateUpload!,
|
||||||
chapter.isBookmarked!
|
ref: ref,
|
||||||
? Icon(Icons.bookmark, size: 16, color: context.primaryColor)
|
context: context,
|
||||||
: Container(),
|
),
|
||||||
Flexible(child: _buildTitle(chapter.name!, context)),
|
style: const TextStyle(fontSize: 11),
|
||||||
],
|
),
|
||||||
),
|
if (!chapter.isRead!)
|
||||||
subtitle: Row(
|
if (chapter.lastPageRead!.isNotEmpty &&
|
||||||
children: [
|
chapter.lastPageRead != "1")
|
||||||
if ((chapter.manga.value!.isLocalArchive ?? false) == false)
|
Row(
|
||||||
Text(
|
children: [
|
||||||
chapter.dateUpload == null || chapter.dateUpload!.isEmpty
|
const Text(' • '),
|
||||||
? ""
|
Text(
|
||||||
: dateFormat(
|
chapter.manga.value!.itemType == ItemType.anime
|
||||||
chapter.dateUpload!,
|
? l10n.episode_progress(
|
||||||
ref: ref,
|
Duration(
|
||||||
context: context,
|
milliseconds: int.parse(
|
||||||
|
chapter.lastPageRead!,
|
||||||
|
),
|
||||||
|
).toString().substringBefore("."),
|
||||||
|
)
|
||||||
|
: l10n.page(
|
||||||
|
chapter.manga.value!.itemType == ItemType.manga
|
||||||
|
? chapter.lastPageRead!
|
||||||
|
: "${((double.tryParse(chapter.lastPageRead!) ?? 0) * 100).toStringAsFixed(0)} %",
|
||||||
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: context.isLight
|
||||||
|
? Colors.black.withValues(alpha: 0.4)
|
||||||
|
: Colors.white.withValues(alpha: 0.3),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
style: const TextStyle(fontSize: 11),
|
],
|
||||||
),
|
),
|
||||||
if (!chapter.isRead!)
|
if (chapter.scanlator?.isNotEmpty ?? false)
|
||||||
if (chapter.lastPageRead!.isNotEmpty &&
|
|
||||||
chapter.lastPageRead != "1")
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Text(' • '),
|
const Text(' • '),
|
||||||
Text(
|
Text(
|
||||||
chapter.manga.value!.itemType == ItemType.anime
|
chapter.scanlator!,
|
||||||
? l10n.episode_progress(
|
|
||||||
Duration(
|
|
||||||
milliseconds: int.parse(chapter.lastPageRead!),
|
|
||||||
).toString().substringBefore("."),
|
|
||||||
)
|
|
||||||
: l10n.page(
|
|
||||||
chapter.manga.value!.itemType == ItemType.manga
|
|
||||||
? chapter.lastPageRead!
|
|
||||||
: "${((double.tryParse(chapter.lastPageRead!) ?? 0) * 100).toStringAsFixed(0)} %",
|
|
||||||
),
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: context.isLight
|
color: chapter.isRead!
|
||||||
? Colors.black.withValues(alpha: 0.4)
|
? context.isLight
|
||||||
: Colors.white.withValues(alpha: 0.3),
|
? Colors.black.withValues(alpha: 0.4)
|
||||||
|
: Colors.white.withValues(alpha: 0.3)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (chapter.scanlator?.isNotEmpty ?? false)
|
],
|
||||||
Row(
|
),
|
||||||
children: [
|
trailing:
|
||||||
const Text(' • '),
|
!sourceExist || (chapter.manga.value!.isLocalArchive ?? false)
|
||||||
Text(
|
? null
|
||||||
chapter.scanlator!,
|
: ChapterPageDownload(chapter: chapter),
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: chapter.isRead!
|
|
||||||
? context.isLight
|
|
||||||
? Colors.black.withValues(alpha: 0.4)
|
|
||||||
: Colors.white.withValues(alpha: 0.3)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
trailing: !sourceExist || (chapter.manga.value!.isLocalArchive ?? false)
|
|
||||||
? null
|
|
||||||
: ChapterPageDownload(chapter: chapter),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleInteraction(WidgetRef ref, [BuildContext? context]) {
|
||||||
|
final isLongPressed = ref.read(isLongPressedStateProvider);
|
||||||
|
if (isLongPressed) {
|
||||||
|
ref.read(chaptersListStateProvider.notifier).update(chapter);
|
||||||
|
} else {
|
||||||
|
if (context != null) {
|
||||||
|
chapter.pushToReaderView(context, ignoreIsRead: true);
|
||||||
|
} else {
|
||||||
|
ref.read(chaptersListStateProvider.notifier).update(chapter);
|
||||||
|
ref.read(isLongPressedStateProvider.notifier).update(!isLongPressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildTitle(String text, BuildContext context) {
|
Widget _buildTitle(String text, BuildContext context) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,12 @@ class _MigrationScreenScreenState extends ConsumerState<MigrationScreen> {
|
||||||
.and()
|
.and()
|
||||||
.itemTypeEqualTo(widget.manga.itemType)
|
.itemTypeEqualTo(widget.manga.itemType)
|
||||||
.findAllSync();
|
.findAllSync();
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final query = widget.manga.name ?? widget.manga.author ?? "";
|
||||||
|
_textEditingController.text = query;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -68,7 +74,6 @@ class _MigrationScreenScreenState extends ConsumerState<MigrationScreen> {
|
||||||
final query = _query.isNotEmpty
|
final query = _query.isNotEmpty
|
||||||
? _query
|
? _query
|
||||||
: widget.manga.name ?? widget.manga.author ?? "";
|
: widget.manga.name ?? widget.manga.author ?? "";
|
||||||
_textEditingController.text = query;
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
|
@ -100,6 +105,7 @@ class _MigrationScreenScreenState extends ConsumerState<MigrationScreen> {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
controller: _textEditingController,
|
controller: _textEditingController,
|
||||||
|
autofocus: false,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -126,6 +132,12 @@ class _MigrationScreenScreenState extends ConsumerState<MigrationScreen> {
|
||||||
: Container(),
|
: Container(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_textEditingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MigrationSourceSearchScreen extends StatefulWidget {
|
class MigrationSourceSearchScreen extends StatefulWidget {
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,13 @@ class _TrackerWidgetSearchState extends ConsumerState<TrackerWidgetSearch> {
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _controller = TextEditingController(text: query);
|
late final _controller = TextEditingController(text: query);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,13 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
|
||||||
return mangaRes;
|
return mangaRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
_textEditingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
late final _textEditingController = TextEditingController(text: widget.query);
|
late final _textEditingController = TextEditingController(text: widget.query);
|
||||||
late String _query = widget.query;
|
late String _query = widget.query;
|
||||||
late bool _isSearch = widget.isSearch;
|
late bool _isSearch = widget.isSearch;
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,12 @@ class SeachFormTextFieldWidget extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SeachFormTextFieldWidgetState extends State<SeachFormTextFieldWidget> {
|
class _SeachFormTextFieldWidgetState extends State<SeachFormTextFieldWidget> {
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
late final _controller = TextEditingController(text: widget.text);
|
late final _controller = TextEditingController(text: widget.text);
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,12 @@ class _DoubleColummViewState extends State<DoubleColummView>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scaleAnimationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
void _toggleScale(Offset tapPosition) {
|
void _toggleScale(Offset tapPosition) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ class MangaChapterPageGallery extends ConsumerStatefulWidget {
|
||||||
|
|
||||||
class _MangaChapterPageGalleryState
|
class _MangaChapterPageGalleryState
|
||||||
extends ConsumerState<MangaChapterPageGallery>
|
extends ConsumerState<MangaChapterPageGallery>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin, WidgetsBindingObserver {
|
||||||
late AnimationController _scaleAnimationController;
|
late AnimationController _scaleAnimationController;
|
||||||
late Animation<double> _animation;
|
late Animation<double> _animation;
|
||||||
late ReaderController _readerController = ref.read(
|
late ReaderController _readerController = ref.read(
|
||||||
|
|
@ -136,6 +136,7 @@ class _MangaChapterPageGalleryState
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
_readerController.setMangaHistoryUpdate();
|
_readerController.setMangaHistoryUpdate();
|
||||||
final index = _uChapDataPreload[_currentIndex!].index;
|
final index = _uChapDataPreload[_currentIndex!].index;
|
||||||
if (index != null) {
|
if (index != null) {
|
||||||
|
|
@ -144,7 +145,15 @@ class _MangaChapterPageGalleryState
|
||||||
|
|
||||||
_rebuildDetail.close();
|
_rebuildDetail.close();
|
||||||
_doubleClickAnimationController.dispose();
|
_doubleClickAnimationController.dispose();
|
||||||
|
_scaleAnimationController.dispose();
|
||||||
|
_failedToLoadImage.dispose();
|
||||||
_autoScroll.value = false;
|
_autoScroll.value = false;
|
||||||
|
_autoScroll.dispose();
|
||||||
|
_autoScrollPage.dispose();
|
||||||
|
_itemPositionsListener.itemPositions.removeListener(_readProgressListener);
|
||||||
|
_photoViewController.dispose();
|
||||||
|
_photoViewScaleStateController.dispose();
|
||||||
|
_extendedController.dispose();
|
||||||
clearGestureDetailsCache();
|
clearGestureDetailsCache();
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
setFullScreen(value: false);
|
setFullScreen(value: false);
|
||||||
|
|
@ -158,6 +167,17 @@ class _MangaChapterPageGalleryState
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.paused ||
|
||||||
|
state == AppLifecycleState.detached) {
|
||||||
|
final index = _uChapDataPreload[_currentIndex!].index;
|
||||||
|
if (index != null) {
|
||||||
|
_readerController.setPageIndex(_geCurrentIndex(index), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
late final _autoScroll = ValueNotifier(
|
late final _autoScroll = ValueNotifier(
|
||||||
_readerController.autoScrollValues().$1,
|
_readerController.autoScrollValues().$1,
|
||||||
);
|
);
|
||||||
|
|
@ -203,6 +223,7 @@ class _MangaChapterPageGalleryState
|
||||||
_itemPositionsListener.itemPositions.addListener(_readProgressListener);
|
_itemPositionsListener.itemPositions.addListener(_readProgressListener);
|
||||||
_initCurrentIndex();
|
_initCurrentIndex();
|
||||||
discordRpc.showChapterDetails(ref, chapter);
|
discordRpc.showChapterDetails(ref, chapter);
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
final double _horizontalScaleValue = 1.0;
|
final double _horizontalScaleValue = 1.0;
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,12 @@ class _ChapterListWidgetState extends State<ChapterListWidget> {
|
||||||
_jumpTo();
|
_jumpTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _jumpTo() async {
|
Future<void> _jumpTo() async {
|
||||||
await Future.delayed(const Duration(milliseconds: 5));
|
await Future.delayed(const Duration(milliseconds: 5));
|
||||||
controller.jumpTo(
|
controller.jumpTo(
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@ class _DownloadFileScreenState extends ConsumerState<DownloadFileScreen> {
|
||||||
final List<int> _bytes = [];
|
final List<int> _bytes = [];
|
||||||
late StreamSubscription<List<int>>? _subscription;
|
late StreamSubscription<List<int>>? _subscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_subscription?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = l10nLocalizations(context)!;
|
final l10n = l10nLocalizations(context)!;
|
||||||
|
|
|
||||||
|
|
@ -24,33 +24,38 @@ class CategoriesScreen extends ConsumerStatefulWidget {
|
||||||
class _CategoriesScreenState extends ConsumerState<CategoriesScreen>
|
class _CategoriesScreenState extends ConsumerState<CategoriesScreen>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
late TabController _tabBarController;
|
late TabController _tabBarController;
|
||||||
int tabs = 3;
|
late final List<String> _tabList;
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tabBarController = TabController(length: tabs, vsync: this);
|
final hideItems = ref.read(hideItemsStateProvider);
|
||||||
|
_tabList = [
|
||||||
|
if (!hideItems.contains("/MangaLibrary")) "/MangaLibrary",
|
||||||
|
if (!hideItems.contains("/AnimeLibrary")) "/AnimeLibrary",
|
||||||
|
if (!hideItems.contains("/NovelLibrary")) "/NovelLibrary",
|
||||||
|
];
|
||||||
|
_tabBarController = TabController(length: _tabList.length, vsync: this);
|
||||||
_tabBarController.animateTo(widget.data.$2);
|
_tabBarController.animateTo(widget.data.$2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tabBarController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
int newTabs = 0;
|
if (_tabList.isEmpty) {
|
||||||
final hideItems = ref.watch(hideItemsStateProvider);
|
return Scaffold(
|
||||||
if (!hideItems.contains("/MangaLibrary")) newTabs++;
|
appBar: AppBar(title: Text(context.l10n.categories)),
|
||||||
if (!hideItems.contains("/AnimeLibrary")) newTabs++;
|
body: Center(child: Text("EMPTY\nMPTY\nMTY\nMT\n\n")),
|
||||||
if (!hideItems.contains("/NovelLibrary")) newTabs++;
|
);
|
||||||
if (tabs != newTabs) {
|
|
||||||
_tabBarController.dispose();
|
|
||||||
_tabBarController = TabController(length: newTabs, vsync: this);
|
|
||||||
_tabBarController.animateTo(0);
|
|
||||||
setState(() {
|
|
||||||
tabs = newTabs;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
final l10n = l10nLocalizations(context)!;
|
final l10n = l10nLocalizations(context)!;
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
animationDuration: Duration.zero,
|
animationDuration: Duration.zero,
|
||||||
length: newTabs,
|
length: _tabList.length,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
|
|
@ -62,23 +67,24 @@ class _CategoriesScreenState extends ConsumerState<CategoriesScreen>
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
indicatorSize: TabBarIndicatorSize.label,
|
indicatorSize: TabBarIndicatorSize.label,
|
||||||
controller: _tabBarController,
|
controller: _tabBarController,
|
||||||
tabs: [
|
tabs: _tabList.map((route) {
|
||||||
if (!hideItems.contains("/MangaLibrary")) Tab(text: l10n.manga),
|
if (route == "/MangaLibrary") return Tab(text: l10n.manga);
|
||||||
if (!hideItems.contains("/AnimeLibrary")) Tab(text: l10n.anime),
|
if (route == "/AnimeLibrary") return Tab(text: l10n.anime);
|
||||||
if (!hideItems.contains("/NovelLibrary")) Tab(text: l10n.novel),
|
return Tab(text: l10n.novel);
|
||||||
],
|
}).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: TabBarView(
|
body: TabBarView(
|
||||||
controller: _tabBarController,
|
controller: _tabBarController,
|
||||||
children: [
|
children: _tabList.map((route) {
|
||||||
if (!hideItems.contains("/MangaLibrary"))
|
if (route == "/MangaLibrary") {
|
||||||
CategoriesTab(itemType: ItemType.manga),
|
return CategoriesTab(itemType: ItemType.manga);
|
||||||
if (!hideItems.contains("/AnimeLibrary"))
|
}
|
||||||
CategoriesTab(itemType: ItemType.anime),
|
if (route == "/AnimeLibrary") {
|
||||||
if (!hideItems.contains("/NovelLibrary"))
|
return CategoriesTab(itemType: ItemType.anime);
|
||||||
CategoriesTab(itemType: ItemType.novel),
|
}
|
||||||
],
|
return CategoriesTab(itemType: ItemType.novel);
|
||||||
|
}).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,12 @@ class _TrackingDetailState extends State<TrackingDetail>
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tabBarController = TabController(length: 2, vsync: this);
|
_tabBarController = TabController(length: 2, vsync: this);
|
||||||
_tabBarController.animateTo(0);
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_tabBarController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,19 @@ class StatisticsScreen extends ConsumerStatefulWidget {
|
||||||
|
|
||||||
class _StatisticsScreenState extends ConsumerState<StatisticsScreen>
|
class _StatisticsScreenState extends ConsumerState<StatisticsScreen>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late final hideItems = ref.read(hideItemsStateProvider);
|
late final List<String> hideItems;
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
|
late final List<String> _tabList;
|
||||||
|
|
||||||
late final _tabList = [
|
|
||||||
if (!hideItems.contains("/MangaLibrary")) 'manga',
|
|
||||||
if (!hideItems.contains("/AnimeLibrary")) 'anime',
|
|
||||||
if (!hideItems.contains("/NovelLibrary")) 'novel',
|
|
||||||
];
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void initState() {
|
||||||
super.didChangeDependencies();
|
super.initState();
|
||||||
|
hideItems = ref.read(hideItemsStateProvider);
|
||||||
|
_tabList = [
|
||||||
|
if (!hideItems.contains("/MangaLibrary")) "/MangaLibrary",
|
||||||
|
if (!hideItems.contains("/AnimeLibrary")) "/AnimeLibrary",
|
||||||
|
if (!hideItems.contains("/NovelLibrary")) "/NovelLibrary",
|
||||||
|
];
|
||||||
_tabController = TabController(length: _tabList.length, vsync: this);
|
_tabController = TabController(length: _tabList.length, vsync: this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +40,10 @@ class _StatisticsScreenState extends ConsumerState<StatisticsScreen>
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_tabList.isEmpty) {
|
if (_tabList.isEmpty) {
|
||||||
return SizedBox.shrink();
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(context.l10n.statistics)),
|
||||||
|
body: Center(child: Text("EMPTY\nMPTY\nMTY\nMT\n\n")),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
@ -46,23 +51,24 @@ class _StatisticsScreenState extends ConsumerState<StatisticsScreen>
|
||||||
title: Text(l10n.statistics),
|
title: Text(l10n.statistics),
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
tabs: [
|
tabs: _tabList.map((route) {
|
||||||
if (!hideItems.contains("/MangaLibrary")) Tab(text: "Manga"),
|
if (route == "/MangaLibrary") return Tab(text: l10n.manga);
|
||||||
if (!hideItems.contains("/AnimeLibrary")) Tab(text: "Anime"),
|
if (route == "/AnimeLibrary") return Tab(text: l10n.anime);
|
||||||
if (!hideItems.contains("/NovelLibrary")) Tab(text: "Novel"),
|
return Tab(text: l10n.novel);
|
||||||
],
|
}).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: TabBarView(
|
body: TabBarView(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: [
|
children: _tabList.map((route) {
|
||||||
if (!hideItems.contains("/MangaLibrary"))
|
if (route == "/MangaLibrary") {
|
||||||
_buildStatisticsTab(itemType: ItemType.manga),
|
return _buildStatisticsTab(itemType: ItemType.manga);
|
||||||
if (!hideItems.contains("/AnimeLibrary"))
|
}
|
||||||
_buildStatisticsTab(itemType: ItemType.anime),
|
if (route == "/AnimeLibrary") {
|
||||||
if (!hideItems.contains("/NovelLibrary"))
|
return _buildStatisticsTab(itemType: ItemType.anime);
|
||||||
_buildStatisticsTab(itemType: ItemType.novel),
|
}
|
||||||
],
|
return _buildStatisticsTab(itemType: ItemType.novel);
|
||||||
|
}).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'package:epubx/epubx.dart';
|
||||||
import 'package:extended_image/extended_image.dart';
|
import 'package:extended_image/extended_image.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_qjs/quickjs/ffi.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
@ -17,6 +19,7 @@ import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_pr
|
||||||
import 'package:mangayomi/modules/novel/novel_reader_controller_provider.dart';
|
import 'package:mangayomi/modules/novel/novel_reader_controller_provider.dart';
|
||||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||||
import 'package:mangayomi/services/get_html_content.dart';
|
import 'package:mangayomi/services/get_html_content.dart';
|
||||||
|
import 'package:mangayomi/utils/extensions/dom_extensions.dart';
|
||||||
import 'package:mangayomi/utils/utils.dart';
|
import 'package:mangayomi/utils/utils.dart';
|
||||||
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
|
import 'package:mangayomi/modules/manga/reader/providers/push_router.dart';
|
||||||
import 'package:mangayomi/services/get_chapter_pages.dart';
|
import 'package:mangayomi/services/get_chapter_pages.dart';
|
||||||
|
|
@ -25,6 +28,8 @@ import 'package:mangayomi/utils/global_style.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
||||||
|
import 'package:html/dom.dart' as dom;
|
||||||
|
import 'package:flutter/widgets.dart' as widgets;
|
||||||
|
|
||||||
typedef DoubleClickAnimationListener = void Function();
|
typedef DoubleClickAnimationListener = void Function();
|
||||||
|
|
||||||
|
|
@ -34,21 +39,17 @@ class NovelReaderView extends ConsumerWidget {
|
||||||
late final Chapter chapter = isar.chapters.getSync(chapterId)!;
|
late final Chapter chapter = isar.chapters.getSync(chapterId)!;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final htmlContent = ref.watch(getHtmlContentProvider(chapter: chapter));
|
final result = ref.watch(getHtmlContentProvider(chapter: chapter));
|
||||||
|
|
||||||
return NovelWebView(chapter: chapter, htmlContent: htmlContent);
|
return NovelWebView(chapter: chapter, result: result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NovelWebView extends ConsumerStatefulWidget {
|
class NovelWebView extends ConsumerStatefulWidget {
|
||||||
const NovelWebView({
|
const NovelWebView({super.key, required this.chapter, required this.result});
|
||||||
super.key,
|
|
||||||
required this.chapter,
|
|
||||||
required this.htmlContent,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Chapter chapter;
|
final Chapter chapter;
|
||||||
final AsyncValue<String> htmlContent;
|
final AsyncValue<(String, EpubBook?)> result;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState createState() {
|
ConsumerState createState() {
|
||||||
|
|
@ -99,6 +100,7 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
|
||||||
}
|
}
|
||||||
|
|
||||||
late Chapter chapter = widget.chapter;
|
late Chapter chapter = widget.chapter;
|
||||||
|
EpubBook? epubBook;
|
||||||
|
|
||||||
final StreamController<double> _rebuildDetail =
|
final StreamController<double> _rebuildDetail =
|
||||||
StreamController<double>.broadcast();
|
StreamController<double>.broadcast();
|
||||||
|
|
@ -206,8 +208,9 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
widget.htmlContent.when(
|
widget.result.when(
|
||||||
data: (htmlContent) {
|
data: (data) {
|
||||||
|
epubBook = data.$2;
|
||||||
Future.delayed(const Duration(milliseconds: 1000), () {
|
Future.delayed(const Duration(milliseconds: 1000), () {
|
||||||
if (!scrolled && _scrollController.hasClients) {
|
if (!scrolled && _scrollController.hasClients) {
|
||||||
_scrollController.animateTo(
|
_scrollController.animateTo(
|
||||||
|
|
@ -223,48 +226,48 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
|
||||||
child: Scrollbar(
|
child: Scrollbar(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
interactive: true,
|
interactive: true,
|
||||||
child: SingleChildScrollView(
|
child: GestureDetector(
|
||||||
controller: _scrollController,
|
behavior: HitTestBehavior.translucent,
|
||||||
physics: const BouncingScrollPhysics(),
|
onTap: () {
|
||||||
child: GestureDetector(
|
_isViewFunction();
|
||||||
behavior: HitTestBehavior.translucent,
|
},
|
||||||
onTap: () {
|
child: CustomScrollView(
|
||||||
_isViewFunction();
|
controller: _scrollController,
|
||||||
},
|
physics: const BouncingScrollPhysics(),
|
||||||
child: Column(
|
slivers: [
|
||||||
children: [
|
HtmlWidget(
|
||||||
HtmlWidget(
|
data.$1,
|
||||||
htmlContent,
|
customWidgetBuilder: (element) =>
|
||||||
customStylesBuilder: (element) {
|
_buildCustomWidgets(element),
|
||||||
switch (backgroundColor) {
|
customStylesBuilder: (element) {
|
||||||
case BackgroundColor.black:
|
switch (backgroundColor) {
|
||||||
return {
|
case BackgroundColor.black:
|
||||||
'background-color': 'black',
|
return {'background-color': 'black'};
|
||||||
};
|
default:
|
||||||
default:
|
return {
|
||||||
return {
|
'background-color': '#F0F0F0',
|
||||||
'background-color': '#F0F0F0',
|
};
|
||||||
};
|
}
|
||||||
}
|
},
|
||||||
},
|
onTapUrl: (url) {
|
||||||
onTapUrl: (url) {
|
context.push(
|
||||||
context.push(
|
"/mangawebview",
|
||||||
"/mangawebview",
|
extra: {'url': url, 'title': url},
|
||||||
extra: {'url': url, 'title': url},
|
);
|
||||||
);
|
return true;
|
||||||
return true;
|
},
|
||||||
},
|
renderMode: RenderMode.sliverList,
|
||||||
renderMode: RenderMode.column,
|
textStyle: TextStyle(
|
||||||
textStyle: TextStyle(
|
color:
|
||||||
color:
|
backgroundColor ==
|
||||||
backgroundColor ==
|
BackgroundColor.white
|
||||||
BackgroundColor.white
|
? Colors.black
|
||||||
? Colors.black
|
: Colors.white,
|
||||||
: Colors.white,
|
fontSize: fontSize.toDouble(),
|
||||||
fontSize: fontSize.toDouble(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Center(
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Center(
|
||||||
heightFactor: 2,
|
heightFactor: 2,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment:
|
||||||
|
|
@ -310,8 +313,8 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -735,6 +738,26 @@ class _NovelWebViewState extends ConsumerState<NovelWebView>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget? _buildCustomWidgets(dom.Element element) {
|
||||||
|
if (element.localName == "img" &&
|
||||||
|
element.getSrc != null &&
|
||||||
|
epubBook != null) {
|
||||||
|
final fileName = element.getSrc!.split("/").last;
|
||||||
|
final image = epubBook!.Content!.Images!.entries
|
||||||
|
.firstWhereOrNull((img) => img.key.endsWith(fileName))
|
||||||
|
?.value
|
||||||
|
.Content;
|
||||||
|
return image != null
|
||||||
|
? widgets.Image(
|
||||||
|
errorBuilder: (context, error, stackTrace) => Text("❌"),
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
image: MemoryImage(image as Uint8List) as ImageProvider,
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UChapDataPreload {
|
class UChapDataPreload {
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,12 @@ class _TrackerLibraryScreenState extends ConsumerState<TrackerLibraryScreen> {
|
||||||
List<TrackLibrarySection> _sections = [];
|
List<TrackLibrarySection> _sections = [];
|
||||||
List<TrackPreference> _preferences = [];
|
List<TrackPreference> _preferences = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_textEditingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = l10nLocalizations(context)!;
|
final l10n = l10nLocalizations(context)!;
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import 'package:mangayomi/eval/model/m_bridge.dart';
|
||||||
import 'package:mangayomi/main.dart';
|
import 'package:mangayomi/main.dart';
|
||||||
import 'package:mangayomi/models/chapter.dart';
|
import 'package:mangayomi/models/chapter.dart';
|
||||||
import 'package:mangayomi/models/update.dart';
|
import 'package:mangayomi/models/update.dart';
|
||||||
import 'package:mangayomi/models/history.dart';
|
|
||||||
import 'package:mangayomi/models/manga.dart';
|
import 'package:mangayomi/models/manga.dart';
|
||||||
import 'package:mangayomi/modules/manga/detail/providers/update_manga_detail_providers.dart';
|
import 'package:mangayomi/modules/manga/detail/providers/update_manga_detail_providers.dart';
|
||||||
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
|
||||||
|
|
@ -34,8 +33,9 @@ class UpdatesScreen extends ConsumerStatefulWidget {
|
||||||
class _UpdatesScreenState extends ConsumerState<UpdatesScreen>
|
class _UpdatesScreenState extends ConsumerState<UpdatesScreen>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
late TabController _tabBarController;
|
late TabController _tabBarController;
|
||||||
|
late final List<String> _tabList;
|
||||||
|
late final List<String> hideItems;
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
int tabs = 3;
|
|
||||||
Future<void> _updateLibrary() async {
|
Future<void> _updateLibrary() async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
|
|
@ -92,13 +92,6 @@ class _UpdatesScreenState extends ConsumerState<UpdatesScreen>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Future.doWhile(() async {
|
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
|
||||||
if (mangaList.length == numbers) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
BotToast.cleanAll();
|
BotToast.cleanAll();
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
|
|
@ -112,213 +105,201 @@ class _UpdatesScreenState extends ConsumerState<UpdatesScreen>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_textEditingController.dispose();
|
||||||
|
_tabBarController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tabBarController = TabController(length: tabs, vsync: this);
|
hideItems = ref.read(hideItemsStateProvider);
|
||||||
_tabBarController.animateTo(0);
|
_tabList = [
|
||||||
|
if (!hideItems.contains("/MangaLibrary")) "/MangaLibrary",
|
||||||
|
if (!hideItems.contains("/AnimeLibrary")) "/AnimeLibrary",
|
||||||
|
if (!hideItems.contains("/NovelLibrary")) "/NovelLibrary",
|
||||||
|
];
|
||||||
|
_tabBarController = TabController(length: _tabList.length, vsync: this);
|
||||||
_tabBarController.addListener(tabListener);
|
_tabBarController.addListener(tabListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
final _textEditingController = TextEditingController();
|
final _textEditingController = TextEditingController();
|
||||||
bool _isSearch = false;
|
bool _isSearch = false;
|
||||||
List<History> entriesData = [];
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
int newTabs = 0;
|
|
||||||
final hideItems = ref.watch(hideItemsStateProvider);
|
|
||||||
if (!hideItems.contains("/MangaLibrary")) newTabs++;
|
|
||||||
if (!hideItems.contains("/AnimeLibrary")) newTabs++;
|
|
||||||
if (!hideItems.contains("/NovelLibrary")) newTabs++;
|
|
||||||
if (newTabs == 0) {
|
|
||||||
return SizedBox.shrink();
|
|
||||||
}
|
|
||||||
if (tabs != newTabs) {
|
|
||||||
_tabBarController.removeListener(tabListener);
|
|
||||||
_tabBarController.dispose();
|
|
||||||
_tabBarController = TabController(length: newTabs, vsync: this);
|
|
||||||
_tabBarController.animateTo(0);
|
|
||||||
_tabBarController.addListener(tabListener);
|
|
||||||
setState(() {
|
|
||||||
tabs = newTabs;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
final l10n = l10nLocalizations(context)!;
|
final l10n = l10nLocalizations(context)!;
|
||||||
return DefaultTabController(
|
return Scaffold(
|
||||||
animationDuration: Duration.zero,
|
appBar: AppBar(
|
||||||
length: newTabs,
|
elevation: 0,
|
||||||
child: Scaffold(
|
backgroundColor: Colors.transparent,
|
||||||
appBar: AppBar(
|
title: _isSearch
|
||||||
elevation: 0,
|
? null
|
||||||
backgroundColor: Colors.transparent,
|
: Text(
|
||||||
title: _isSearch
|
l10n.updates,
|
||||||
? null
|
style: TextStyle(color: Theme.of(context).hintColor),
|
||||||
: Text(
|
|
||||||
l10n.updates,
|
|
||||||
style: TextStyle(color: Theme.of(context).hintColor),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
_isSearch
|
|
||||||
? SeachFormTextField(
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
onSuffixPressed: () {
|
|
||||||
_textEditingController.clear();
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_isSearch = false;
|
|
||||||
});
|
|
||||||
_textEditingController.clear();
|
|
||||||
},
|
|
||||||
controller: _textEditingController,
|
|
||||||
)
|
|
||||||
: IconButton(
|
|
||||||
splashRadius: 20,
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_isSearch = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
Icons.search_outlined,
|
|
||||||
color: Theme.of(context).hintColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
splashRadius: 20,
|
|
||||||
onPressed: () {
|
|
||||||
_updateLibrary();
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
Icons.refresh_outlined,
|
|
||||||
color: Theme.of(context).hintColor,
|
|
||||||
),
|
),
|
||||||
),
|
actions: [
|
||||||
IconButton(
|
_isSearch
|
||||||
splashRadius: 20,
|
? SeachFormTextField(
|
||||||
onPressed: () {
|
onChanged: (value) {
|
||||||
showDialog(
|
setState(() {});
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text(l10n.remove_everything),
|
|
||||||
content: Text(l10n.remove_all_update_msg),
|
|
||||||
actions: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
child: Text(l10n.cancel),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 15),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => clearUpdates(hideItems, context),
|
|
||||||
child: Text(l10n.ok),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
onSuffixPressed: () {
|
||||||
},
|
_textEditingController.clear();
|
||||||
icon: Icon(
|
setState(() {});
|
||||||
Icons.delete_sweep_outlined,
|
},
|
||||||
color: Theme.of(context).hintColor,
|
onPressed: () {
|
||||||
),
|
setState(() {
|
||||||
|
_isSearch = false;
|
||||||
|
});
|
||||||
|
_textEditingController.clear();
|
||||||
|
},
|
||||||
|
controller: _textEditingController,
|
||||||
|
)
|
||||||
|
: IconButton(
|
||||||
|
splashRadius: 20,
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_isSearch = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.search_outlined,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
splashRadius: 20,
|
||||||
|
onPressed: () {
|
||||||
|
_updateLibrary();
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.refresh_outlined,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
splashRadius: 20,
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(l10n.remove_everything),
|
||||||
|
content: Text(l10n.remove_all_update_msg),
|
||||||
|
actions: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Text(l10n.cancel),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 15),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (mounted) Navigator.pop(context);
|
||||||
|
await _clearUpdates(hideItems);
|
||||||
|
},
|
||||||
|
child: Text(l10n.ok),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.delete_sweep_outlined,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
bottom: TabBar(
|
||||||
|
indicatorSize: TabBarIndicatorSize.tab,
|
||||||
|
controller: _tabBarController,
|
||||||
|
tabs: [
|
||||||
|
if (!hideItems.contains("/MangaLibrary"))
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Tab(text: l10n.manga),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
_updateNumbers(ref, ItemType.manga),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (!hideItems.contains("/AnimeLibrary"))
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Tab(text: l10n.anime),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
_updateNumbers(ref, ItemType.anime),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (!hideItems.contains("/NovelLibrary"))
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Tab(text: l10n.novel),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
_updateNumbers(ref, ItemType.novel),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
bottom: TabBar(
|
|
||||||
indicatorSize: TabBarIndicatorSize.tab,
|
|
||||||
controller: _tabBarController,
|
|
||||||
tabs: [
|
|
||||||
if (!hideItems.contains("/MangaLibrary"))
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Tab(text: l10n.manga),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
_updateNumbers(ref, ItemType.manga),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (!hideItems.contains("/AnimeLibrary"))
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Tab(text: l10n.anime),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
_updateNumbers(ref, ItemType.anime),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (!hideItems.contains("/NovelLibrary"))
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Tab(text: l10n.novel),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
_updateNumbers(ref, ItemType.novel),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
body: Padding(
|
),
|
||||||
padding: const EdgeInsets.only(top: 10),
|
body: Padding(
|
||||||
child: TabBarView(
|
padding: const EdgeInsets.only(top: 10),
|
||||||
controller: _tabBarController,
|
child: TabBarView(
|
||||||
children: [
|
controller: _tabBarController,
|
||||||
if (!hideItems.contains("/MangaLibrary"))
|
children: [
|
||||||
UpdateTab(
|
if (!hideItems.contains("/MangaLibrary"))
|
||||||
itemType: ItemType.manga,
|
UpdateTab(
|
||||||
query: _textEditingController.text,
|
itemType: ItemType.manga,
|
||||||
isLoading: _isLoading,
|
query: _textEditingController.text,
|
||||||
),
|
isLoading: _isLoading,
|
||||||
if (!hideItems.contains("/AnimeLibrary"))
|
),
|
||||||
UpdateTab(
|
if (!hideItems.contains("/AnimeLibrary"))
|
||||||
itemType: ItemType.anime,
|
UpdateTab(
|
||||||
query: _textEditingController.text,
|
itemType: ItemType.anime,
|
||||||
isLoading: _isLoading,
|
query: _textEditingController.text,
|
||||||
),
|
isLoading: _isLoading,
|
||||||
if (!hideItems.contains("/NovelLibrary"))
|
),
|
||||||
UpdateTab(
|
if (!hideItems.contains("/NovelLibrary"))
|
||||||
itemType: ItemType.novel,
|
UpdateTab(
|
||||||
query: _textEditingController.text,
|
itemType: ItemType.novel,
|
||||||
isLoading: _isLoading,
|
query: _textEditingController.text,
|
||||||
),
|
isLoading: _isLoading,
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearUpdates(List<String> hideItems, BuildContext context) {
|
Future<void> _clearUpdates(List<String> hideItems) async {
|
||||||
List<Update> updates = isar.updates
|
List<Update> updates = await isar.updates
|
||||||
.filter()
|
.filter()
|
||||||
.idIsNotNull()
|
.idIsNotNull()
|
||||||
.chapter(
|
.chapter(
|
||||||
(q) =>
|
(q) =>
|
||||||
q.manga((q) => q.itemTypeEqualTo(getCurrentItemType(hideItems))),
|
q.manga((q) => q.itemTypeEqualTo(getCurrentItemType(hideItems))),
|
||||||
)
|
)
|
||||||
.findAllSync()
|
.findAll();
|
||||||
.toList();
|
final idsToDelete = <Id>[];
|
||||||
isar.writeTxnSync(() {
|
for (var update in updates) {
|
||||||
for (var update in updates) {
|
idsToDelete.add(update.id!);
|
||||||
isar.updates.deleteSync(update.id!);
|
ref
|
||||||
ref
|
.read(synchingProvider(syncId: 1).notifier)
|
||||||
.read(synchingProvider(syncId: 1).notifier)
|
.addChangedPart(ActionType.removeUpdate, update.id, "{}", false);
|
||||||
.addChangedPart(ActionType.removeUpdate, update.id, "{}", false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (mounted) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
}
|
||||||
|
await isar.writeTxn(() => isar.updates.deleteAll(idsToDelete));
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemType getCurrentItemType(List<String> hideItems) {
|
ItemType getCurrentItemType(List<String> hideItems) {
|
||||||
|
|
@ -347,9 +328,14 @@ class UpdateTab extends ConsumerStatefulWidget {
|
||||||
ConsumerState<UpdateTab> createState() => _UpdateTabState();
|
ConsumerState<UpdateTab> createState() => _UpdateTabState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _UpdateTabState extends ConsumerState<UpdateTab> {
|
class _UpdateTabState extends ConsumerState<UpdateTab>
|
||||||
|
with AutomaticKeepAliveClientMixin {
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
final l10n = l10nLocalizations(context)!;
|
final l10n = l10nLocalizations(context)!;
|
||||||
final update = ref.watch(
|
final update = ref.watch(
|
||||||
getAllUpdateStreamProvider(
|
getAllUpdateStreamProvider(
|
||||||
|
|
@ -357,105 +343,103 @@ class _UpdateTabState extends ConsumerState<UpdateTab> {
|
||||||
search: widget.query,
|
search: widget.query,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return Scaffold(
|
return Stack(
|
||||||
body: Stack(
|
children: [
|
||||||
children: [
|
update.when(
|
||||||
update.when(
|
data: (entries) {
|
||||||
data: (entries) {
|
final lastUpdatedList = entries
|
||||||
final lastUpdatedList = entries
|
.map((e) => e.chapter.value!.manga.value!.lastUpdate!)
|
||||||
.map((e) => e.chapter.value!.manga.value!.lastUpdate!)
|
.toList();
|
||||||
.toList();
|
lastUpdatedList.sort((a, b) => b.compareTo(a));
|
||||||
lastUpdatedList.sort((a, b) => b.compareTo(a));
|
final lastUpdated = lastUpdatedList.firstOrNull;
|
||||||
final lastUpdated = lastUpdatedList.firstOrNull;
|
if (entries.isNotEmpty) {
|
||||||
if (entries.isNotEmpty) {
|
return CustomScrollView(
|
||||||
return CustomScrollView(
|
slivers: [
|
||||||
slivers: [
|
if (lastUpdated != null)
|
||||||
if (lastUpdated != null)
|
SliverPadding(
|
||||||
SliverPadding(
|
padding: const EdgeInsets.only(
|
||||||
padding: const EdgeInsets.only(
|
left: 10,
|
||||||
left: 10,
|
right: 10,
|
||||||
right: 10,
|
top: 10,
|
||||||
top: 10,
|
bottom: 20,
|
||||||
bottom: 20,
|
|
||||||
),
|
|
||||||
sliver: SliverList(
|
|
||||||
delegate: SliverChildListDelegate.fixed([
|
|
||||||
Text(
|
|
||||||
l10n.library_last_updated(
|
|
||||||
dateFormat(
|
|
||||||
lastUpdated.toString(),
|
|
||||||
ref: ref,
|
|
||||||
context: context,
|
|
||||||
showHOURorMINUTE: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
style: TextStyle(
|
|
||||||
fontStyle: FontStyle.italic,
|
|
||||||
color: context.secondaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
CustomSliverGroupedListView<Update, String>(
|
sliver: SliverList(
|
||||||
elements: entries,
|
delegate: SliverChildListDelegate.fixed([
|
||||||
groupBy: (element) => dateFormat(
|
Text(
|
||||||
element.date!,
|
l10n.library_last_updated(
|
||||||
context: context,
|
|
||||||
ref: ref,
|
|
||||||
forHistoryValue: true,
|
|
||||||
useRelativeTimesTamps: false,
|
|
||||||
),
|
|
||||||
groupSeparatorBuilder: (String groupByValue) => Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 8, left: 12),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
dateFormat(
|
dateFormat(
|
||||||
null,
|
lastUpdated.toString(),
|
||||||
context: context,
|
|
||||||
stringDate: groupByValue,
|
|
||||||
ref: ref,
|
ref: ref,
|
||||||
|
context: context,
|
||||||
|
showHOURorMINUTE: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
style: TextStyle(
|
||||||
),
|
fontStyle: FontStyle.italic,
|
||||||
|
color: context.secondaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
itemBuilder: (context, element) {
|
|
||||||
final chapter = element.chapter.value!;
|
|
||||||
return UpdateChapterListTileWidget(
|
|
||||||
chapter: chapter,
|
|
||||||
sourceExist: true,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
itemComparator: (item1, item2) =>
|
|
||||||
item1.date!.compareTo(item2.date!),
|
|
||||||
order: GroupedListOrder.DESC,
|
|
||||||
),
|
),
|
||||||
],
|
CustomSliverGroupedListView<Update, String>(
|
||||||
);
|
elements: entries,
|
||||||
}
|
groupBy: (element) => dateFormat(
|
||||||
return Center(child: Text(l10n.no_recent_updates));
|
element.date!,
|
||||||
},
|
context: context,
|
||||||
error: (Object error, StackTrace stackTrace) {
|
ref: ref,
|
||||||
return ErrorText(error);
|
forHistoryValue: true,
|
||||||
},
|
useRelativeTimesTamps: false,
|
||||||
loading: () {
|
),
|
||||||
return const ProgressCenter();
|
groupSeparatorBuilder: (String groupByValue) => Padding(
|
||||||
},
|
padding: const EdgeInsets.only(bottom: 8, left: 12),
|
||||||
),
|
child: Row(
|
||||||
if (widget.isLoading)
|
children: [
|
||||||
const Positioned(
|
Text(
|
||||||
top: 0,
|
dateFormat(
|
||||||
left: 0,
|
null,
|
||||||
right: 0,
|
context: context,
|
||||||
child: Padding(
|
stringDate: groupByValue,
|
||||||
padding: EdgeInsets.only(top: 40),
|
ref: ref,
|
||||||
child: Center(child: RefreshProgressIndicator()),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
itemBuilder: (context, element) {
|
||||||
|
final chapter = element.chapter.value!;
|
||||||
|
return UpdateChapterListTileWidget(
|
||||||
|
chapter: chapter,
|
||||||
|
sourceExist: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemComparator: (item1, item2) =>
|
||||||
|
item1.date!.compareTo(item2.date!),
|
||||||
|
order: GroupedListOrder.DESC,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Center(child: Text(l10n.no_recent_updates));
|
||||||
|
},
|
||||||
|
error: (Object error, StackTrace stackTrace) {
|
||||||
|
return ErrorText(error);
|
||||||
|
},
|
||||||
|
loading: () {
|
||||||
|
return const ProgressCenter();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (widget.isLoading)
|
||||||
|
const Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(top: 40),
|
||||||
|
child: Center(child: RefreshProgressIndicator()),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,17 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (Platform.isLinux) {
|
||||||
|
_desktopWebview?.close();
|
||||||
|
} else {
|
||||||
|
if (browser.isOpened()) browser.close();
|
||||||
|
browser.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Webview? _desktopWebview;
|
Webview? _desktopWebview;
|
||||||
_runWebViewDesktop() async {
|
_runWebViewDesktop() async {
|
||||||
if (Platform.isLinux) {
|
if (Platform.isLinux) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:draggable_menu/draggable_menu.dart';
|
import 'package:draggable_menu/draggable_menu.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -64,10 +65,8 @@ Future<void> customDraggableTabBar({
|
||||||
index = tabBarController.index;
|
index = tabBarController.index;
|
||||||
if (index != currentIndex) {
|
if (index != currentIndex) {
|
||||||
index = currentIndex;
|
index = currentIndex;
|
||||||
refresh();
|
|
||||||
} else {
|
|
||||||
refresh();
|
|
||||||
}
|
}
|
||||||
|
refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
await showDialog(
|
await showDialog(
|
||||||
|
|
@ -79,10 +78,7 @@ Future<void> customDraggableTabBar({
|
||||||
for (var i = 0; i < children.length; i++) ...[
|
for (var i = 0; i < children.length; i++) ...[
|
||||||
MeasureWidgetSize(
|
MeasureWidgetSize(
|
||||||
onCalculateSize: (size) {
|
onCalculateSize: (size) {
|
||||||
final additionnalHeight = ((List.generate(
|
final additionnalHeight = Random().nextDouble() * 0.01;
|
||||||
10000,
|
|
||||||
(index) => index * 0.0001,
|
|
||||||
))..shuffle()).first;
|
|
||||||
double newHeight = size!.height + 52.0 + additionnalHeight;
|
double newHeight = size!.height + 52.0 + additionnalHeight;
|
||||||
if (!(newHeight <= maxHeight)) {
|
if (!(newHeight <= maxHeight)) {
|
||||||
newHeight = maxHeight + additionnalHeight;
|
newHeight = maxHeight + additionnalHeight;
|
||||||
|
|
@ -212,4 +208,5 @@ Future<void> customDraggableTabBar({
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
tabBarController.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:epubx/epubx.dart';
|
||||||
import 'package:html/parser.dart';
|
import 'package:html/parser.dart';
|
||||||
import 'package:mangayomi/eval/lib.dart';
|
import 'package:mangayomi/eval/lib.dart';
|
||||||
import 'package:mangayomi/models/chapter.dart';
|
import 'package:mangayomi/models/chapter.dart';
|
||||||
|
|
@ -10,10 +11,27 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
part 'get_html_content.g.dart';
|
part 'get_html_content.g.dart';
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<String> getHtmlContent(Ref ref, {required Chapter chapter}) async {
|
Future<(String, EpubBook?)> getHtmlContent(
|
||||||
|
Ref ref, {
|
||||||
|
required Chapter chapter,
|
||||||
|
}) async {
|
||||||
if (!chapter.manga.isLoaded) {
|
if (!chapter.manga.isLoaded) {
|
||||||
chapter.manga.loadSync();
|
chapter.manga.loadSync();
|
||||||
}
|
}
|
||||||
|
if (chapter.archivePath != null && chapter.archivePath!.isNotEmpty) {
|
||||||
|
final htmlFile = File(chapter.archivePath!);
|
||||||
|
if (await htmlFile.exists()) {
|
||||||
|
final bytes = await htmlFile.readAsBytes();
|
||||||
|
final book = await EpubReader.readBook(bytes);
|
||||||
|
final tempChapter = book.Chapters?.where(
|
||||||
|
(element) => element.Title!.isNotEmpty
|
||||||
|
? element.Title == chapter.name
|
||||||
|
: "Book" == chapter.name,
|
||||||
|
).firstOrNull;
|
||||||
|
return (_buildHtml(tempChapter?.HtmlContent ?? "No content"), book);
|
||||||
|
}
|
||||||
|
return (_buildHtml("Local epub file not found!"), null);
|
||||||
|
}
|
||||||
final storageProvider = StorageProvider();
|
final storageProvider = StorageProvider();
|
||||||
final mangaDirectory = await storageProvider.getMangaMainDirectory(chapter);
|
final mangaDirectory = await storageProvider.getMangaMainDirectory(chapter);
|
||||||
final htmlPath = "${mangaDirectory!.path}${chapter.name}.html";
|
final htmlPath = "${mangaDirectory!.path}${chapter.name}.html";
|
||||||
|
|
@ -37,7 +55,11 @@ Future<String> getHtmlContent(Ref ref, {required Chapter chapter}) async {
|
||||||
source!,
|
source!,
|
||||||
).getHtmlContent(chapter.manga.value!.name!, chapter.url!);
|
).getHtmlContent(chapter.manga.value!.name!, chapter.url!);
|
||||||
}
|
}
|
||||||
return '''<div id="readerViewContent"><div style="padding: 2em;">${html.substring(1, html.length - 1)}</div></div>'''
|
return (_buildHtml(html.substring(1, html.length - 1)), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _buildHtml(String input) {
|
||||||
|
return '''<div id="readerViewContent"><div style="padding: 2em;">$input</div></div>'''
|
||||||
.replaceAll("\\n", "")
|
.replaceAll("\\n", "")
|
||||||
.replaceAll("\\t", "")
|
.replaceAll("\\t", "")
|
||||||
.replaceAll("\\\"", "\"");
|
.replaceAll("\\\"", "\"");
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part of 'get_html_content.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$getHtmlContentHash() => r'6bdc17222f959cb5f91b56027d4f98e26571175d';
|
String _$getHtmlContentHash() => r'19e6959d8fceb065b19c6c6d38cd1b5132a8ba94';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|
@ -34,7 +34,7 @@ class _SystemHash {
|
||||||
const getHtmlContentProvider = GetHtmlContentFamily();
|
const getHtmlContentProvider = GetHtmlContentFamily();
|
||||||
|
|
||||||
/// See also [getHtmlContent].
|
/// See also [getHtmlContent].
|
||||||
class GetHtmlContentFamily extends Family<AsyncValue<String>> {
|
class GetHtmlContentFamily extends Family<AsyncValue<(String, EpubBook?)>> {
|
||||||
/// See also [getHtmlContent].
|
/// See also [getHtmlContent].
|
||||||
const GetHtmlContentFamily();
|
const GetHtmlContentFamily();
|
||||||
|
|
||||||
|
|
@ -72,7 +72,8 @@ class GetHtmlContentFamily extends Family<AsyncValue<String>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See also [getHtmlContent].
|
/// See also [getHtmlContent].
|
||||||
class GetHtmlContentProvider extends AutoDisposeFutureProvider<String> {
|
class GetHtmlContentProvider
|
||||||
|
extends AutoDisposeFutureProvider<(String, EpubBook?)> {
|
||||||
/// See also [getHtmlContent].
|
/// See also [getHtmlContent].
|
||||||
GetHtmlContentProvider({
|
GetHtmlContentProvider({
|
||||||
required Chapter chapter,
|
required Chapter chapter,
|
||||||
|
|
@ -107,7 +108,7 @@ class GetHtmlContentProvider extends AutoDisposeFutureProvider<String> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Override overrideWith(
|
Override overrideWith(
|
||||||
FutureOr<String> Function(GetHtmlContentRef provider) create,
|
FutureOr<(String, EpubBook?)> Function(GetHtmlContentRef provider) create,
|
||||||
) {
|
) {
|
||||||
return ProviderOverride(
|
return ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
|
|
@ -124,7 +125,7 @@ class GetHtmlContentProvider extends AutoDisposeFutureProvider<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AutoDisposeFutureProviderElement<String> createElement() {
|
AutoDisposeFutureProviderElement<(String, EpubBook?)> createElement() {
|
||||||
return _GetHtmlContentProviderElement(this);
|
return _GetHtmlContentProviderElement(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,13 +145,14 @@ class GetHtmlContentProvider extends AutoDisposeFutureProvider<String> {
|
||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
mixin GetHtmlContentRef on AutoDisposeFutureProviderRef<String> {
|
mixin GetHtmlContentRef on AutoDisposeFutureProviderRef<(String, EpubBook?)> {
|
||||||
/// The parameter `chapter` of this provider.
|
/// The parameter `chapter` of this provider.
|
||||||
Chapter get chapter;
|
Chapter get chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GetHtmlContentProviderElement
|
class _GetHtmlContentProviderElement
|
||||||
extends AutoDisposeFutureProviderElement<String> with GetHtmlContentRef {
|
extends AutoDisposeFutureProviderElement<(String, EpubBook?)>
|
||||||
|
with GetHtmlContentRef {
|
||||||
_GetHtmlContentProviderElement(super.provider);
|
_GetHtmlContentProviderElement(super.provider);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ class MyAnimeList extends _$MyAnimeList {
|
||||||
publishingType: res["media_type"].toString().replaceAll("_", " "),
|
publishingType: res["media_type"].toString().replaceAll("_", " "),
|
||||||
publishingStatus: res["status"].toString().replaceAll("_", " "),
|
publishingStatus: res["status"].toString().replaceAll("_", " "),
|
||||||
trackingUrl: "https://myanimelist.net/$item/${res["id"]}",
|
trackingUrl: "https://myanimelist.net/$item/${res["id"]}",
|
||||||
score: res["mean"],
|
score: (res["mean"] as num?)?.toDouble(),
|
||||||
syncId: syncId,
|
syncId: syncId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -171,32 +171,23 @@ class MyAnimeList extends _$MyAnimeList {
|
||||||
|
|
||||||
return res['data'] == null
|
return res['data'] == null
|
||||||
? []
|
? []
|
||||||
: (res['data'] as List)
|
: (res['data'] as List).map((e) {
|
||||||
.map(
|
final node = e["node"] as Map<String, dynamic>;
|
||||||
(e) => TrackSearch(
|
String clean(String? s) => (s ?? '').replaceAll('_', ' ');
|
||||||
mediaId: e["node"]["id"],
|
return TrackSearch(
|
||||||
summary: e["node"]["synopsis"] ?? "",
|
mediaId: node["id"],
|
||||||
totalChapter: e["node"][contentUnit],
|
summary: node["synopsis"] ?? "",
|
||||||
coverUrl: e["node"]["main_picture"]["large"] ?? "",
|
totalChapter: node[contentUnit],
|
||||||
title: e["node"]["title"],
|
coverUrl: node["main_picture"]["large"] ?? "",
|
||||||
score: e["node"]["mean"] is double
|
title: node["title"],
|
||||||
? e["node"]["mean"]
|
score: (node["mean"] as num?)?.toDouble(),
|
||||||
: ((e["node"]["mean"] ?? 0) as int).toDouble(),
|
startDate: node["start_date"] ?? "",
|
||||||
startDate: e["node"]["start_date"] ?? "",
|
publishingType: clean(node["media_type"].toString()),
|
||||||
publishingType: e["node"]["media_type"].toString().replaceAll(
|
publishingStatus: clean(node["status"].toString()),
|
||||||
"_",
|
trackingUrl: "https://myanimelist.net/$item/${node["id"]}",
|
||||||
" ",
|
syncId: syncId,
|
||||||
),
|
);
|
||||||
publishingStatus: e["node"]["status"].toString().replaceAll(
|
}).toList();
|
||||||
"_",
|
|
||||||
" ",
|
|
||||||
),
|
|
||||||
trackingUrl:
|
|
||||||
"https://myanimelist.net/$item/${e["node"]["id"]}",
|
|
||||||
syncId: syncId,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<TrackSearch>> fetchUserData({bool isManga = true}) async {
|
Future<List<TrackSearch>> fetchUserData({bool isManga = true}) async {
|
||||||
|
|
@ -219,46 +210,31 @@ class MyAnimeList extends _$MyAnimeList {
|
||||||
|
|
||||||
return res['data'] == null
|
return res['data'] == null
|
||||||
? []
|
? []
|
||||||
: (res['data'] as List)
|
: (res['data'] as List).map((e) {
|
||||||
.map(
|
final node = e["node"] as Map<String, dynamic>;
|
||||||
(e) => TrackSearch(
|
final listStatus = e["list_status"] as Map<String, dynamic>;
|
||||||
mediaId: e["node"]["id"],
|
String clean(String? s) => (s ?? '').replaceAll('_', ' ');
|
||||||
summary: e["node"]["synopsis"] ?? "",
|
return TrackSearch(
|
||||||
totalChapter: e["node"][contentUnit],
|
mediaId: node["id"],
|
||||||
coverUrl: e["node"]["main_picture"]["large"] ?? "",
|
summary: node["synopsis"] ?? "",
|
||||||
title: e["node"]["title"],
|
totalChapter: node[contentUnit],
|
||||||
score: e["node"]["mean"] is double
|
coverUrl: node["main_picture"]["large"] ?? "",
|
||||||
? e["node"]["mean"]
|
title: node["title"],
|
||||||
: ((e["node"]["mean"] ?? 0) as int).toDouble(),
|
score: (node["mean"] as num?)?.toDouble(),
|
||||||
startDate: e["node"]["start_date"] ?? "",
|
startDate: node["start_date"] ?? "",
|
||||||
publishingType: e["node"]["media_type"].toString().replaceAll(
|
publishingType: clean(node["media_type"].toString()),
|
||||||
"_",
|
publishingStatus: clean(node["status"].toString()),
|
||||||
" ",
|
trackingUrl: "https://myanimelist.net/$item/${node["id"]}",
|
||||||
),
|
startedReadingDate: _parseDate(listStatus["start_date"]),
|
||||||
publishingStatus: e["node"]["status"].toString().replaceAll(
|
finishedReadingDate: _parseDate(listStatus["finish_date"]),
|
||||||
"_",
|
lastChapterRead:
|
||||||
" ",
|
listStatus[isManga
|
||||||
),
|
? "num_chapters_read"
|
||||||
trackingUrl:
|
: "num_episodes_watched"],
|
||||||
"https://myanimelist.net/$item/${e["node"]["id"]}",
|
status: fromMyAnimeListStatus(listStatus["status"], isManga).name,
|
||||||
startedReadingDate: _parseDate(
|
syncId: syncId,
|
||||||
e["list_status"]["start_date"],
|
);
|
||||||
),
|
}).toList();
|
||||||
finishedReadingDate: _parseDate(
|
|
||||||
e["list_status"]["finish_date"],
|
|
||||||
),
|
|
||||||
lastChapterRead:
|
|
||||||
e["list_status"][isManga
|
|
||||||
? "num_chapters_read"
|
|
||||||
: "num_episodes_watched"],
|
|
||||||
status: fromMyAnimeListStatus(
|
|
||||||
e["list_status"]["status"],
|
|
||||||
isManga,
|
|
||||||
).name,
|
|
||||||
syncId: syncId,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _convertToIsoDate(int? epochTime) {
|
String _convertToIsoDate(int? epochTime) {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part of 'myanimelist.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$myAnimeListHash() => r'8e21378e2a3ccdf696a0a1c9a88a05123f65eacb';
|
String _$myAnimeListHash() => r'a612e9ce814268ac79dc86d810ca6bd3671812e6';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|
|
||||||
1
linux/packaging/icons/hicolor/128x128/apps/mangayomi.png
Symbolic link
1
linux/packaging/icons/hicolor/128x128/apps/mangayomi.png
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
|
||||||
1
linux/packaging/icons/hicolor/16x16/apps/mangayomi.png
Symbolic link
1
linux/packaging/icons/hicolor/16x16/apps/mangayomi.png
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
|
||||||
1
linux/packaging/icons/hicolor/256x256/apps/mangayomi.png
Symbolic link
1
linux/packaging/icons/hicolor/256x256/apps/mangayomi.png
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
|
||||||
1
linux/packaging/icons/hicolor/32x32/apps/mangayomi.png
Symbolic link
1
linux/packaging/icons/hicolor/32x32/apps/mangayomi.png
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
|
||||||
1
linux/packaging/icons/hicolor/512x512/apps/mangayomi.png
Symbolic link
1
linux/packaging/icons/hicolor/512x512/apps/mangayomi.png
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
|
||||||
1
linux/packaging/icons/hicolor/64x64/apps/mangayomi.png
Symbolic link
1
linux/packaging/icons/hicolor/64x64/apps/mangayomi.png
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
|
||||||
Loading…
Reference in a new issue