diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.yml b/.github/ISSUE_TEMPLATE/01_bug_report.yml new file mode 100644 index 000000000..e9670e9de --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01_bug_report.yml @@ -0,0 +1,85 @@ +name: Bug Report +description: 'Any bug about core nzbget, EXCLUDING extensions, performance issues or compiling nzbget.' +labels: ['bug'] +body: +- type: checkboxes + attributes: + label: Is there already an issue for your problem? + description: Please make sure you are not creating an already submitted Issue. Check closed issues as well, because your issue may have already been fixed. + options: + - label: I have checked older issues, open and closed + required: true +- type: dropdown + attributes: + label: NZBGet Version + description: Which version of NZBGet has this bug? + options: + - v23-stable + - v23-testing + - v22-stable (nzbgetcom takeover) + - v22-testing (nzbgetcom takeover) + - v21 or earlier (orignal nzbget) + validations: + required: true +- type: dropdown + attributes: + label: Platform + description: Select a specific platform for this bug report, choose All if it applies to all platforms + options: + - All + - Windows + - macOS + - NAS/Synology/QNAP + - Linux/Docker + validations: + required: true +- type: textarea + attributes: + label: Environment + description: Please provide the details of the system NZBGet is running on. The more information you can provide, the better + placeholder: | + Device: + OS version: platform n.nn (32bit/64bit) + CPU architecture: (32bit/64bit) + Includes libs or tool that apply: 7zip x.x, unrar x.x, python3.11 + Running in Docker: No/Yes + render: markdown + validations: + required: true +- type: textarea + attributes: + label: Current Behavior + description: A concise description of what you're experiencing. + validations: + required: true +- type: textarea + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: true +- type: textarea + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. In this environment... + 2. With this config... + 3. Run '...' + 4. See error... + validations: + required: false +- type: textarea + attributes: + label: Logs + description: | + Any logs or messages that apply + validations: + required: false +- type: textarea + attributes: + label: Extra information + description: | + Please refer to any extra information - extra documentation, attach files or images + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/02_feature_request.yml b/.github/ISSUE_TEMPLATE/02_feature_request.yml new file mode 100644 index 000000000..903c175ca --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02_feature_request.yml @@ -0,0 +1,36 @@ +name: Feature Request +description: 'Feature requests or small enhancements' +labels: ['enhancement'] +body: +- type: checkboxes + attributes: + label: Is there already an issue for this request? + description: Please make sure you are not creating an already submitted Issue. Check closed issues as well, because your issue could have been resolved and waiting to be released. + options: + - label: I have checked older issues, open and closed + required: true +- type: dropdown + attributes: + label: Platform + description: Select a specific platform for this request, choose All if it applies to all platforms + options: + - All + - Windows + - macOS + - NAS/Synology/QNAP + - Linux/Docker + validations: + required: true +- type: textarea + attributes: + label: Describe the enhancement you'd like + description: A clear and concise description of what you want to happen. + validations: + required: true +- type: textarea + attributes: + label: Extra information + description: | + Please refer to any extra information - competing product(s) and/or features, extra documentation, attach files or images + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/03_build.yml b/.github/ISSUE_TEMPLATE/03_build.yml new file mode 100644 index 000000000..73f9f918a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03_build.yml @@ -0,0 +1,71 @@ +name: Building, compiling, contributing +description: 'Problems building nzbget or questions about contributing' +labels: ['question'] +body: +- type: dropdown + attributes: + label: NZBGet Version + description: Version of NZBGet for the scope of this issue + options: + - v23-stable + - v23-testing + - v22-stable (nzbgetcom takeover) + - v22-testing (nzbgetcom takeover) + - v21 or earlier (orignal nzbget) + validations: + required: true +- type: dropdown + attributes: + label: Platform + description: Select a specific platform for this issue, choose All if it applies to all platforms + options: + - All + - Windows + - macOS + - NAS/Synology/QNAP + - Linux/Docker + validations: + required: true +- type: textarea + attributes: + label: Environment + description: Please provide the details of the system where the issue is observed + placeholder: | + Device: + OS version: platform n.nn (32bit/64bit) + CPU architecture: (32bit/64bit) + Includes libs or tool that apply: 7zip x.x, unrar x.x, python3.11 + render: markdown + validations: + required: true +- type: textarea + attributes: + label: Problem or Question + description: Describe the problem, expected behavior + validations: + required: true +- type: textarea + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. In this environment... + 2. With this config... + 3. Run '...' + 4. See error... + validations: + required: false +- type: textarea + attributes: + label: Logs + description: | + Any logs or messages that apply + validations: + required: false +- type: textarea + attributes: + label: Extra information + description: | + Please refer to any extra information - extra documentation, attach files or images + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/04_extension.yml b/.github/ISSUE_TEMPLATE/04_extension.yml new file mode 100644 index 000000000..31437b8d5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/04_extension.yml @@ -0,0 +1,25 @@ +# this flow should definitely be impoved with a form - separate suggestions, +# extensions master list and actual extensions questions, TBD + +name: Extension +description: 'Questions, bugs, requests related to Extensions' +labels: ['extension'] +body: +- type: checkboxes + attributes: + label: Is there already an issue for this request? + description: Please make sure you are not creating an already submitted Issue. Check closed issues as well, because your issue could have been resolved and waiting to be released. + options: + - label: I have checked older issues, open and closed + required: true +- type: textarea + attributes: + label: Describe your issue + description: Describe the issue with the extension. Provide as much information as possible + placeholder: | + Existing extension - details about environment - version of nzbget - version of extension - related libs or tools if any. + Please provide as much useful information as possible + .. + Or suggest new extension to be supported with Extension Manager + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..33f5c5936 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Discussions + url: https://github.com/nzbgetcom/nzbget/discussions + about: Discuss, suggest. Any questions, including performance issues. + - name: NZBGet.com Website + url: https://nzbget.com/contact/ + about: Contact Us via Email diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e628e6177..a9895bf94 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,8 @@ jobs: build-osx: uses: ./.github/workflows/osx.yml + permissions: + actions: write build-synology: uses: ./.github/workflows/synology.yml @@ -23,6 +25,14 @@ jobs: build-qnap: uses: ./.github/workflows/qnap.yml + build-linux-pkg: + uses: ./.github/workflows/linux-pkg.yml + with: + external_call: true + needs: [build-linux] + permissions: + actions: write + repack-qnap: uses: ./.github/workflows/qnap-repack.yml with: @@ -35,7 +45,7 @@ jobs: env: PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} runs-on: ubuntu-latest - needs: [build-windows, build-linux, build-osx, build-synology, build-qnap, repack-qnap] + needs: [build-windows, build-linux, build-osx, build-synology, build-qnap, repack-qnap, build-linux-pkg] permissions: actions: write steps: @@ -52,6 +62,8 @@ jobs: mv nzbget-synology-packages/* builds || true mv nzbget-qnap-packages/* builds || true mv nzbget-qnap-native-packages/* builds || true + mv nzbget-deb-packages/* builds || true + mv nzbget-rpm-packages/* builds || true cd builds VERSION=$(ls | grep bin-windows-setup | cut -d - -f 2) if [ "$GITHUB_REF_NAME" != "main" ]; then VERSION="$VERSION-testing"; fi @@ -63,7 +75,7 @@ jobs: echo "nzbget_signatures({" | tee $SIGS_FILE echo | tee -a $SIGS_FILE - for FILE in *.exe *.run *.zip *.spk *.qpkg; do + for FILE in *.exe *.run *.zip *.spk *.qpkg *.deb *.rpm; do [ -f $FILE ] || continue MD5=$(openssl dgst -md5 $FILE | cut -d ' ' -f 2) @@ -103,8 +115,15 @@ jobs: nzbget-qnap-packages nzbget-qnap-native-packages + - name: Delete unneded linux packages artifacts + uses: geekyeggo/delete-artifact@v4 + with: + name: | + nzbget-deb-packages + nzbget-rpm-packages + make-testing-release: - runs-on: [self-hosted, linux] + runs-on: [self-hosted, nzbget-linux] needs: [generate-signatures] permissions: contents: write diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6092c0ab0..0b71377ff 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -54,7 +54,11 @@ jobs: if [[ "$TAG" == "" ]]; then TAG="${GITHUB_REF_NAME/\//-}" fi - TAGS="${{ env.REGISTRY_IMAGE }}:$TAG,ghcr.io/${{ env.REGISTRY_IMAGE }}:$TAG" + if [[ "$GITHUB_REF_NAME" == "develop" ]] || [[ "$GITHUB_REF_NAME" == "main" ]] || [[ $GITHUB_REF == 'refs/tags/'* ]]; then + TAGS="${{ env.REGISTRY_IMAGE }}:$TAG,ghcr.io/${{ env.REGISTRY_IMAGE }}:$TAG" + else + TAGS="${{ env.REGISTRY_IMAGE }}:$TAG" + fi echo "tags=$TAGS" >> $GITHUB_OUTPUT echo "version=$TAG" >> $GITHUB_OUTPUT @@ -72,7 +76,8 @@ jobs: "MAKE_JOBS=2" - name: Update Docker Hub Description - uses: peter-evans/dockerhub-description@v3 + if: github.ref_name == 'main' + uses: peter-evans/dockerhub-description@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/linux-pkg.yml b/.github/workflows/linux-pkg.yml new file mode 100644 index 000000000..f91c68da3 --- /dev/null +++ b/.github/workflows/linux-pkg.yml @@ -0,0 +1,59 @@ +name: linux packages + +on: + workflow_call: + inputs: + external_call: + description: 'To distinguish workflow_call from regular push / workflow_dispatch' + type: boolean + required: false + default: false + workflow_dispatch: + +jobs: + build-linux: + uses: ./.github/workflows/linux.yml + if: ${{ inputs.external_call == false }} + + build-pkg: + runs-on: [self-hosted, nzbget-linux] + needs: [build-linux] + if: always() + permissions: + actions: write + + steps: + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download build artifacts + uses: actions/download-artifact@v4 + + - name: Build DEB and RPM packages + run: | + sudo apt-get update && sudo apt-get install rpm -y + bash linux/pkg/build-pkg.sh + + - name: Upload DEB build artifacts + uses: actions/upload-artifact@v4 + with: + name: nzbget-deb-packages + path: build/deb/*.deb + retention-days: 5 + + - name: Upload RPM build artifacts + uses: actions/upload-artifact@v4 + with: + name: nzbget-rpm-packages + path: build/rpm/*.rpm + retention-days: 5 + + - name: Delete unneded linux artifacts + if: ${{ inputs.external_call == false }} + uses: geekyeggo/delete-artifact@v4 + with: + name: | + nzbget-linux-installers diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9e618da22..5e6e9989e 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -6,7 +6,7 @@ on: jobs: build: - runs-on: [self-hosted, linux] + runs-on: [self-hosted, nzbget-linux] steps: @@ -29,7 +29,12 @@ jobs: rm -rf /build/output cp -r . /build/nzbget cd /build - docker run -e ALL_ARCHS="i686 x86_64 aarch64 armhf armel mipseb mipsel ppc6xx ppc500" -v /build:/build nzbget-build /build/scripts/build-nzbget-ci.sh + if [ "$GITHUB_REF_NAME" == "develop" ] || [ "$GITHUB_REF_NAME" == "main" ]; then + DEBUG=yes + else + DEBUG=no + fi + ALL_ARCHS="i686 x86_64 aarch64 armhf armel mipseb mipsel ppc6xx ppc500 riscv64" DEBUG=$DEBUG /build/scripts/build-nzbget-ci.sh - name: Rename build artifacts if: github.ref_name != 'main' && github.ref_name != 'develop' diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml index f4f1c4909..87ff84e83 100644 --- a/.github/workflows/osx.yml +++ b/.github/workflows/osx.yml @@ -5,8 +5,44 @@ on: workflow_dispatch: jobs: - build: - runs-on: [self-hosted, macos] + build-x64: + runs-on: [self-hosted, macos, x64] + + steps: + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build + run: | + if [ "$GITHUB_REF_NAME" != "main" ]; then + bash osx/build-nzbget-x64.sh testing + else + bash osx/build-nzbget-x64.sh + fi + + - name: Rename build artifacts + if: github.ref_name != 'main' && github.ref_name != 'develop' + run: | + cd build + SUFFIX="${GITHUB_REF_NAME/\//-}" + for FILE in *.zip; do + [ -f $FILE ] || continue + NEW_FILE=${FILE/-bin-macos-x64.zip/-$SUFFIX-bin-macos-x64.zip} + mv $FILE $NEW_FILE + done + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: nzbget-osx-installers-x64 + path: build/*.zip + retention-days: 5 + + build-universal: + runs-on: [self-hosted, macos, arm64] steps: @@ -25,7 +61,7 @@ jobs: - name: Build run: | - bash osx/build-nzbget.sh + bash osx/build-nzbget-universal.sh - name: Rename build artifacts if: github.ref_name != 'main' && github.ref_name != 'develop' @@ -41,6 +77,36 @@ jobs: - name: Upload build artifacts uses: actions/upload-artifact@v4 with: - name: nzbget-osx-installers + name: nzbget-osx-installers-universal path: osx/build/Release/*.zip retention-days: 5 + + combine-osx-artifacts: + runs-on: ubuntu-latest + needs: [build-x64, build-universal] + permissions: + actions: write + steps: + + - name: Download build artifacts + uses: actions/download-artifact@v4 + + - name: Combine artifacts + run: | + mkdir -p nzbget-osx-installers + mv nzbget-osx-installers-x64/* nzbget-osx-installers + mv nzbget-osx-installers-universal/* nzbget-osx-installers + + - name: Upload build artifacts with signatures + uses: actions/upload-artifact@v4 + with: + name: nzbget-osx-installers + path: nzbget-osx-installers/* + retention-days: 5 + + - name: Delete unneded artifacts + uses: geekyeggo/delete-artifact@v4 + with: + name: | + nzbget-osx-installers-x64 + nzbget-osx-installers-universal diff --git a/.github/workflows/qnap-repack.yml b/.github/workflows/qnap-repack.yml index a628e9fd2..091385628 100644 --- a/.github/workflows/qnap-repack.yml +++ b/.github/workflows/qnap-repack.yml @@ -16,7 +16,7 @@ jobs: if: ${{ inputs.external_call == false }} repack: - runs-on: [self-hosted, linux] + runs-on: [self-hosted, nzbget-qnap] needs: [build-linux] if: always() permissions: diff --git a/.github/workflows/qnap.yml b/.github/workflows/qnap.yml index 896794598..258fb7029 100644 --- a/.github/workflows/qnap.yml +++ b/.github/workflows/qnap.yml @@ -6,7 +6,7 @@ on: jobs: build: - runs-on: [self-hosted, linux] + runs-on: [self-hosted, nzbget-qnap] steps: diff --git a/.github/workflows/synology.yml b/.github/workflows/synology.yml index 78b98b08e..1f996f3f3 100644 --- a/.github/workflows/synology.yml +++ b/.github/workflows/synology.yml @@ -6,7 +6,7 @@ on: jobs: build: - runs-on: [self-hosted, linux] + runs-on: [self-hosted, nzbget-synology] steps: diff --git a/.github/workflows/windows-tests.yml b/.github/workflows/windows-tests.yml index a449de7ed..8fed2240d 100644 --- a/.github/workflows/windows-tests.yml +++ b/.github/workflows/windows-tests.yml @@ -31,7 +31,7 @@ jobs: cmake --version mkdir build cd build - cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows-static + cmake .. -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static -DBUILD_ONLY_TESTS=ON cmake --build . --config Release -j 2 - name: Test diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 97b91e6d9..1a755298c 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -13,33 +13,29 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Change version for non-release - if: github.ref_name != 'main' - run: | - $Version = ((((Select-String -Path nzbget.vcxproj -Pattern ";VERSION=")[0] -split(';'))[2] -split('='))[1]) -replace '"', '' - $Date=Get-Date -Format "yyyyMMdd" - $NewVersion = "$Version-testing-$Date" - (Get-Content nzbget.vcxproj) | ForEach-Object {$_ -replace "VERSION=`"$Version`"", "VERSION=`"$NewVersion`""} | Set-Content nzbget.vcxproj - (Get-Content windows\nzbget-setup.nsi) | ForEach-Object {$_ -replace "`"DisplayVersion`" `"$Version`"", "`"DisplayVersion`" `"$NewVersion`""} | Set-Content windows\nzbget-setup.nsi - "NEW_VERSION=$NewVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - name: Build run: | - .\windows\build-nzbget-vs22.bat + $BuildParams="-BuildRelease -Build32 -Build64 -BuildSetup" + If (-not ($env:GITHUB_REF_NAME -eq "main")) { + $BuildParams="$BuildParams -BuildTesting" + } + If (($env:GITHUB_REF_NAME -eq "main") -or ($env:GITHUB_REF_NAME -eq "develop")) { + $BuildParams="$BuildParams -BuildDebug" + } + Invoke-Expression "windows\build-nzbget.ps1 $BuildParams" - name: Rename build artifacts if: github.ref_name != 'main' && github.ref_name != 'develop' run: | - $Output="c:\nzbget\build\output" - $NewVersion=$env:NEW_VERSION + $Output="build" $Suffix = $env:GITHUB_REF_NAME.Replace("/","-") ForEach ($File In Get-ChildItem -Path $Output -Filter "*.exe") { - Rename-Item -Path "$Output\$($File.Name)" -NewName $File.Name.Replace($NewVersion, "$NewVersion-$Suffix") + Rename-Item -Path "$Output\$($File.Name)" -NewName $File.Name.Replace("-bin-windows-setup.exe", "-$Suffix-bin-windows-setup.exe") } - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: nzbget-windows-installers - path: C:\nzbget\build\output\*.exe + path: build\*.exe retention-days: 5 diff --git a/.gitignore b/.gitignore index a5d1eec1d..f10cec75f 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,6 @@ # GNU Autotools .deps/ config.h -config.h.in config.h.in~ configure configure~ diff --git a/CMakeLists.txt b/CMakeLists.txt index f7fed97c3..f5a5d1da3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,57 +1,129 @@ -cmake_minimum_required(VERSION 3.22) +cmake_minimum_required(VERSION 3.13) -set(VERSION "23.0") - -project( - nzbget - VERSION ${VERSION} - DESCRIPTION "NZBGet is a binary downloader, which downloads files from Usenet" - LANGUAGES C CXX -) +if (PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) + message(FATAL_ERROR "In-source builds are not allowed. You should create separate directory for build files.") +endif() -option(ENABLE_TESTS "Enable tests" ON) +set_property(GLOBAL PROPERTY PACKAGE) +set_property(GLOBAL PROPERTY LIBS) +set_property(GLOBAL PROPERTY INCLUDES) +set(VERSION "24.0") set(PACKAGE "nzbget") -add_compile_definitions(HAVE_CONFIG_H=1) - -configure_file( - ${CMAKE_SOURCE_DIR}/cmake_config.h.in - ${CMAKE_BINARY_DIR}/config.h -) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_C_STANDARD 17) +set(PACKAGE_BUGREPORT "https://github.com/nzbgetcom/nzbget/issues") +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_C_EXTENSIONS OFF) -set(BUILD_SHARED_LIBS OFF) -set(OPENSSL_USE_STATIC_LIBS ON) -set(ZLIB_USE_STATIC_LIBS ON) -set(Boost_USE_STATIC_LIBS ON) -set(Boost_USE_MULTITHREADED ON) -set(Boost_USE_STATIC_RUNTIME OFF) -set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") - -find_package(OpenSSL REQUIRED) -find_package(ZLIB REQUIRED) -find_package(LibXml2 REQUIRED) -find_package(Boost REQUIRED COMPONENTS json) - -if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -Weverything") -elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -Wall") -elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /O2 /W4") - set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} winmm.lib /NODEFAULTLIB:msvcrt.lib;libcmt.lib;msvcrtd.lib") +set(CMAKE_CONFIGURATION_TYPES "Release" "Debug") + +string(REGEX MATCH "^([0-9]+)\\.([0-9]+)" VERSION_MATCH ${VERSION}) +set(VERSION_MAJOR ${CMAKE_MATCH_1}) +set(VERSION_MINOR ${CMAKE_MATCH_2}) + +add_compile_definitions(HAVE_CONFIG_H=1) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") endif() -include_directories(lib/regex) +option(BUILD_ONLY_TESTS "Build only tests (for CI)") + +project( + nzbget + VERSION ${VERSION} + DESCRIPTION "NZBGet is a binary downloader, which downloads files from Usenet" + LANGUAGES C CXX +) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") + set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g0 -pthread -g -DDEBUG -Weverything -Wno-c++98-compat" CACHE STRING "" FORCE) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "-O0 -g0 -pthread -g -DDEBUG -Wall -Wextra" CACHE STRING "" FORCE) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(CMAKE_CXX_FLAGS "/Od /Zi /MTd /MP /W4 /EHs /DDEBUG /D_DEBUG /DWIN32 /wd4800 /wd4267" CACHE STRING "" FORCE) + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} winmm.lib Dbghelp.lib libcpmtd.lib" CACHE STRING "" FORCE) + endif() +elseif(CMAKE_BUILD_TYPE STREQUAL "Release") + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang") + set(CMAKE_CXX_FLAGS "-O2 -g0 -pthread -DNDEBUG -Weverything -Wno-c++98-compat" CACHE STRING "" FORCE) + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-s" CACHE STRING "" FORCE) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "-O2 -g0 -pthread -DNDEBUG -Wall -Wextra" CACHE STRING "" FORCE) + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-s" CACHE STRING "" FORCE) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(CMAKE_CXX_FLAGS "/O2 /MT /MP /W4 /EHs /DNDEBUG /DWIN32 /wd4800 /wd4267" CACHE STRING "" FORCE) + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} winmm.lib" CACHE STRING "" FORCE) + endif() +endif() + +set(CMAKE_C_FLAGS_DEBUG ${CMAKE_CXX_FLAGS} CACHE STRING "" FORCE) +set(CMAKE_C_FLAGS_RELEASE ${CMAKE_CXX_FLAGS} CACHE STRING "" FORCE) +set(CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS} CACHE STRING "" FORCE) +set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS} CACHE STRING "" FORCE) + include_directories(${CMAKE_BINARY_DIR}) -add_subdirectory(lib) +include_directories(${CMAKE_SOURCE_DIR}) + +if(NOT BUILD_ONLY_TESTS) + include(daemon/sources.cmake) + add_executable(${PACKAGE} ${SRC}) +endif() + +if(WIN32) + include(cmake/windows.cmake) + if(NOT BUILD_ONLY_TESTS) + target_sources(${PACKAGE} PRIVATE ${CMAKE_SOURCE_DIR}/windows/resources/nzbget.rc) + configure_file( + ${CMAKE_SOURCE_DIR}/windows/resources/version.rc.in + ${CMAKE_BINARY_DIR}/version.rc + ) + configure_file( + ${CMAKE_SOURCE_DIR}/windows/version.nsi.in + ${CMAKE_BINARY_DIR}/version.nsi + @ONLY + ) + configure_file( + ${CMAKE_SOURCE_DIR}/windows/version-uninstall.nsi.in + ${CMAKE_BINARY_DIR}/version-uninstall.nsi + @ONLY + ) + target_sources(${PACKAGE} PRIVATE ${CMAKE_BINARY_DIR}/version.rc) + endif() +else() + include(cmake/posix.cmake) + if(NOT BUILD_ONLY_TESTS) + include(${CMAKE_SOURCE_DIR}/cmake/install.cmake) + endif() +endif() + +configure_file( + ${CMAKE_SOURCE_DIR}/cmake/config.h.in + ${CMAKE_BINARY_DIR}/config.h +) + +if(NOT BUILD_ONLY_TESTS) + target_link_libraries(${PACKAGE} PRIVATE ${LIBS}) + target_include_directories(${PACKAGE} PRIVATE + ${CMAKE_SOURCE_DIR}/daemon/connect + ${CMAKE_SOURCE_DIR}/daemon/extension + ${CMAKE_SOURCE_DIR}/daemon/feed + ${CMAKE_SOURCE_DIR}/daemon/frontend + ${CMAKE_SOURCE_DIR}/daemon/main + ${CMAKE_SOURCE_DIR}/daemon/nntp + ${CMAKE_SOURCE_DIR}/daemon/nserv + ${CMAKE_SOURCE_DIR}/daemon/postprocess + ${CMAKE_SOURCE_DIR}/daemon/queue + ${CMAKE_SOURCE_DIR}/daemon/remote + ${CMAKE_SOURCE_DIR}/daemon/util + ${INCLUDES} + ) +endif() -if(ENABLE_TESTS) - include(CTest) - add_subdirectory(tests) +if(ENABLE_TESTS OR BUILD_ONLY_TESTS) + include(CTest) + add_subdirectory(tests) endif() diff --git a/ChangeLog b/ChangeLog.md similarity index 97% rename from ChangeLog rename to ChangeLog.md index 2b7bf2a2d..78edfdf2c 100644 --- a/ChangeLog +++ b/ChangeLog.md @@ -1,3 +1,63 @@ +nzbget-24.0 + - Features: + - Dark theme and new icons + [#214](https://github.com/nzbgetcom/nzbget/commit/16ecaa5c3eb2efc43e965b796a4c75cdf1d959da); + - Added macOS x64 build support (macOS Mojave 10.14+) + [#194](https://github.com/nzbgetcom/nzbget/commit/90a89f8fb3b81432e192203e8a66419cfd3cd723); + - DEB/RPM packages support + [#230](https://github.com/nzbgetcom/nzbget/commit/1de712a428c031513431d0d75db7ca5ac51d3caa); + * [Instructions](https://nzbgetcom.github.io/). + - NewsServer Add UI - Default encryption and ports + [#225](https://github.com/nzbgetcom/nzbget/commit/cd1cab44b6052c3b02f14689fbafb65505f67a4e): + * moved Server.Encryption between Server.Host and Server.Port; + * made Server.Encryption to ON by default; + * made port depend on Server.Encryption value unless user has put something up: + - 563/443 for secure; + - 119/80 for unsecure. + - Improved error messages and help text in Extension Manager + [#166](https://github.com/nzbgetcom/nzbget/commit/d11ed84912fc486e6f717e56ccd973526920c0db): + * added 7-Zip exit codes decoder according to 7-Zip + [doc](https://documentation.help/7-Zip/exit_codes.htm); + * added a warning that SevenZipCmd may not be valid, in case of extension installation problems. + - Fixed stable/test release notifications + [#181](https://github.com/nzbgetcom/nzbget/commit/1c03b719f88ece7d67ad348f3aca0d3800ab4d54): + * added automatic checking for new testing releases; + * fixed notifications about the new stable/testing release; + * fixed "Uncaught ReferenceError: installedRev" error. + + - Fixed and update links in webui + [#177](https://github.com/nzbgetcom/nzbget/commit/0cc6023bfdf7a1d22311dea7d81b8a6ea7a7d880): + * fixed license links; + * fixed links from nzbget.conf. + + - For developers: + - Moved to CMake. Autotools and MSBuild are deprecated now and may be removed in future versions + [#182](https://github.com/nzbgetcom/nzbget/commit/56e4225fc73a6d1c7cdc6f647a4cac297e28e9f3): + * switched to CMake from autotools and MSBuild, which will simplify cross-platform development; + * fixed installing/uninstalling on FreeBSD and macOS via autotools/CMake; + * added automatic installation of Boost.Json; + * added support for static code analyzer Clang-Tidy. + - Revised and updated documentation + [#199](https://github.com/nzbgetcom/nzbget/commit/c93b551b1fa0afae458c1e78485c6d3eb6410514): + * moved CONTRIBUTING.md to docs/; + * split INSTALLATION.md to POSIX.md, WINDOWS.md and HOW_TO_USE.md; + * added Extensions docs; + * added an instruction on how CPPCheck can be used with CMake. + - Docker: support native unrar building + [#231](https://github.com/nzbgetcom/nzbget/commit/cc6fb8b21a0332aac81b94e466c909e06743235b). + + - Bug fixes: + - Fixed buffer overflow in CString::AppendFmtV + [#208](https://github.com/nzbgetcom/nzbget/commit/c8c59277cfcf085596e124209a3b07e56ada00dc); + - Fixed files/dirs permissions overridden by the unpacker + [#229](https://github.com/nzbgetcom/nzbget/commit/4c92e423fe54a9194b84a9dcd7942865a7df72ec); + - Fixed dynamic addition of extension options + [#209](https://github.com/nzbgetcom/nzbget/commit/d49dc6dc426f71d87626820afd45348643a41458); + - Removed changing ownership at the beginning of container start + [#218](https://github.com/nzbgetcom/nzbget/commit/36dad2f793a5503871f39eca3d7dbdfa945824ee); + - Docker: fixed fresh install + [#219](https://github.com/nzbgetcom/nzbget/commit/a95269e8e15f42196a1314603fb802f3bedd4fa8). + nzbget-23.0 - Features: - Extension Manager @@ -56,7 +116,7 @@ nzbget-23.0 - "Strict" -> test failed; - "Minimal" -> test failed; - "None" -> test passed"; - - Bug fixies: + - Bug fixes: - possible memory corruption [#148](https://github.com/nzbgetcom/nzbget/commit/16ab25d5780c8100309c44eb60d505afe5c528db); - For developers: @@ -1989,7 +2049,7 @@ nzbget-11.0: selected files of source download; - new command in web-interface in edit download dialog on page ; - - new action in remote command <--edit/-E>; + - new action `` in remote command <--edit/-E>; - new action in JSON-/XML-RPC method ; - added support for manual par-check: - if option is set to and a damaged download @@ -2551,7 +2611,7 @@ nzbget-0.7.0: post-processing parameters for nzb-file; - redesigned server pool and par-checker to avoid using of semaphores (which are very platform specific); - - added subcommand to remote commands <--pause/-P> and <--unpause/-U> to + - added subcommand `` to remote commands <--pause/-P> and <--unpause/-U> to pause/unpause the scanning of incoming nzb-directory; - added commands and for scheduler option ; @@ -2668,7 +2728,7 @@ nzbget-0.6.0: the same purpose; updated example configuration file and example postprocess-script to indicate new method of passing arguments via environment variables; - - added subcommands , and to command line switch <-L/--list>, + - added subcommands , and `` to command line switch <-L/--list>, which prints list of files, groups or only status info respectively; extended binary communication protocol to transfer nzb-infos in addition to file-infos; diff --git a/INSTALLATION.md b/INSTALLATION.md index 2dd0fdefc..ff1d0448b 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -1,508 +1,8 @@ -# NZBGet installation +## Platforms -This is a short documentation. For more information please -visit NZBGet home page at - https://nzbget.com +Articles below provide detailed information on manual compilation of NZBGet for specific platforms: -Contents --------- -1. About NZBGet -2. Supported OS -3. Prerequisites on POSIX -4. Installation on POSIX -5. Compiling on Windows -6. Configuration -7. Usage -8. Authors -9. Copyright -10. Contact - - -## 1. About NZBGet - -NZBGet is a binary newsgrabber, which downloads files from usenet -based on information given in nzb-files. NZBGet can be used in -standalone and in server/client modes. In standalone mode you -pass a nzb-file as parameter in command-line, NZBGet downloads -listed files and then exits. - -In server/client mode NZBGet runs as server in background. -Then you use client to send requests to server. The sample requests -are: download nzb-file, list files in queue, etc. - -There is also a built-in web-interface. The server has RPC-support -and can be controlled from third party applications too. - -Standalone-tool, server and client are all contained in only one -executable file "nzbget". The mode in which the program works -depends on command-line parameters passed to the program. - -## 2. Supported OS - -NZBGet is written in C++ and works on Windows, OS X, Linux and -most POSIX-conform OS'es. - -Clients and servers running on different OS'es may communicate with -each other. For example, you can use NZBGet as client on Windows to -control your NZBGet-server running on Linux. - -The download-section of NZBGet web-site provides binary files -for Windows, OS X and Linux. For most POSIX-systems you need to compile -the program yourself. - -If you have downloaded binaries you can just jump to section -"Configuration". - -## 3. Prerequisites on POSIX - -NZBGet is developed on a linux-system, but it runs on other -POSIX platforms. - -NZBGet absolutely needs the following libraries: - - - libstdc++ (usually part of compiler) - - [libxml2](https://gitlab.gnome.org/GNOME/libxml2/-/wikis/home) - - [Boost.JSON](https://www.boost.org/doc/libs/1_84_0/libs/json/doc/html/index.html) - - [Boost.Optional](https://www.boost.org/doc/libs/1_84_0/libs/optional/doc/html/index.html) - -And the following libraries are optional: - - For curses-output-mode (enabled by default): - - libcurses (usually part of commercial systems) - or (better) - - [libncurses](https://invisible-island.net/ncurses) - - For encrypted connections (TLS/SSL): - - [OpenSSL](https://www.openssl.org) - - or - - [GnuTLS](https://gnutls.org) - - For gzip support in web-server and web-client (enabled by default): - - [zlib](https://www.zlib.net/) - - For configuration: - - [autotools](https://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html) - - [autoconf](https://www.gnu.org/software/autoconf/) - - For managing package dependencies: - - [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) - - For tests: - - [Boost.Test](https://www.boost.org/doc/libs/1_84_0/libs/test/doc/html/index.html) - -All these libraries are included in modern POSIX distributions and -should be available as installable packages. Please note that you also -need the developer packages for these libraries too, they package names -have often suffix "dev" or "devel". On other systems you may need to -download the libraries at the given URLs and compile them (see hints below). - -## 4. Installation on POSIX - -Installation from the source distribution archive (nzbget-VERSION.tar.gz): - - - untar the nzbget-source via - tar -zxf nzbget-VERSION.tar.gz - - - change into nzbget-directory via - cd nzbget-VERSION - - - configure it via - - autoreconf --install - - ./configure - - (maybe you have to tell configure, where to find some libraries. - ./configure --help is your friend! - also see "Configure-options" later) - - - in a case you don't have root access or want to install the program - in your home directory use the configure parameter --prefix, e. g.: - - ./configure --prefix ~/usr - - - compile it via - make - - - to install system wide become root via: - su - - - install it via: - make install - - - install configuration files into /etc via: - make install-conf - - (you can skip this step if you intend to store configuration - files in a non-standard location) - -### Configure-options ---------------------- -You may run configure with additional arguments: - - --disable-curses - to make without curses-support. Use this option - if you can not use curses/ncurses. - - --disable-parcheck - to make without parcheck-support. Use this option - if you have troubles when compiling par2-module. - - --with-tlslib=(OpenSSL, GnuTLS) - to select which TLS/SSL library - should be used for encrypted server connections. - - --disable-tls - to make without TLS/SSL support. Use this option if - you can not neither OpenSSL nor GnuTLS. - - --disable-gzip - to make without gzip support. Use this option - if you can not use zlib. - - --enable-debug - to build in debug-mode, if you want to see and log - debug-messages. - -### Optional package: par-check -------------------------------- -NZBGet can check and repair downloaded files for you. For this purpose -it uses library par2. - -For your convenience the source code of libpar2 is integrated into -NZBGet’s source tree and is compiled automatically when you make NZBGet. - -In a case errors occur during this process the inclusion of par2-module -can be disabled using configure option "--disable-parcheck": - - ./configure --disable-parcheck - -### Optional package: curses ----------------------------- -For curses-outputmode you need ncurses or curses on your system. -If you do not have one of them you can download and compile ncurses yourself. -Following configure-parameters may be useful: - - --with-libcurses-includes=/path/to/curses/includes - --with-libcurses-libraries=/path/to/curses/libraries - -If you are not able to use curses or ncurses or do not want them you can -make the program without support for curses using option "--disable-curses": - - ./configure --disable-curses - -### Optional package: TLS -------------------------- -To enable encrypted server connections (TLS/SSL) you need to build the program -with TLS/SSL support. NZBGet can use two libraries: OpenSSL or GnuTLS. -Configure-script checks which library is installed and use it. If both are -available it gives the precedence to OpenSSL. You may override that with -the option --with-tlslib=(OpenSSL, GnuTLS). For example to build with GnuTLS: - - ./configure --with-tlslib= GnuTLS - -Following configure-parameters may be useful: - - --with-libtls-includess=/path/to/gnutls/includes - --with-libtls-libraries=/path/to/gnutls/libraries - - --with-openssl-includess=/path/to/openssl/includes - --with-openssl-libraries=/path/to/openssl/libraries - -If none of these libraries is available you can make the program without -TLS/SSL support using option "--disable-tls": - - ./configure --disable-tls - -## 5. Compiling on Windows - -NZBGet is developed using MS Visual Studio 2015 (Community Edition). The project -file is provided. - -To compile the program with TLS/SSL support you need either OpenSSL or GnuTLS: - - [OpenSSL](https://www.openssl.org) - - or - - [GnuTLS](https://gnutls.org/) - -Also required are: - - [Regex](https://regexlib.com/) - - [Zlib](https://gnuwin32.sourceforge.net/packages/zlib.htm) - - [libxml2](https://gitlab.gnome.org/GNOME/libxml2/-/wikis/home) - - [Boost.JSON](https://www.boost.org/doc/libs/1_84_0/libs/json/doc/html/index.html) - - [Boost.Optional](https://www.boost.org/doc/libs/1_84_0/libs/optional/doc/html/index.html) - -For tests: - - [Boost.Test](https://www.boost.org/doc/libs/1_84_0/libs/test/doc/html/index.html) - -We recommend using [vcpkg](https://vcpkg.io/) to install dependencies. - -## 6. Configuration - -NZBGet needs a configuration file. - -An example configuration file is provided in "nzbget.conf", which -is installed into "/share/nzbget" (where depends on -system configuration and configure options - typically "/usr/local", -"/usr" or "/opt"). The installer adjusts the file according to your -system paths. If you have performed the installation step -"make install-conf" this file is already copied to "/etc" and -NZBGet finds it automatically. If you install the program manually -from a binary archive you have to copy the file from "/share/nzbget" -to one of the locations listed below. - -Open the file in a text editor and modify it accodring to your needs. - -You need to set at least the option "MAINDIR" and one news server in -configuration file. The file has comments on how to use each option. - -The program looks for configuration file in following standard -locations (in this order): - -On POSIX systems: - /nzbget.conf - ~/.nzbget - /etc/nzbget.conf - /usr/etc/nzbget.conf - /usr/local/etc/nzbget.conf - /opt/etc/nzbget.conf - -On Windows: - \nzbget.conf - -If you put the configuration file in other place, you can use command- -line switch "-c " to point the program to correct location. - -In special cases you can run program without configuration file using -switch "-n". You need to use switch "-o" to pass required configuration -options via command-line. - -## 7. Usage - -NZBGet can be used in either standalone mode which downloads a single file -or as a server which is able to queue up numerous download requests. - -TIP for Windows users: NZBGet is controlled via various command line -parameters. For easier using there is a simple shell script included -in "nzbget-shell.bat". Start this script from Windows Explorer and you will -be running a command shell with PATH adjusted to find NZBGet executable. -Then you can type all commands without full path to nzbget.exe. - -### Standalone mode: --------------------- - -nzbget - -### Server mode: ----------------- - -First start the nzbget-server: - - - in console mode: - - nzbget -s - - - or in daemon mode (POSIX only): - - nzbget -D - - - or as a service (Windowx only, firstly install the service with command - "nzbget -install"): - - net start NZBGet - -To stop server use: - - nzbget -Q - -TIP for POSIX users: with included script "nzbgetd" you can use standard -commands to control daemon: - - nzbgetd start - nzbgetd stop - etc. - -When NZBGet is started in console server mode it displays a message that -it is ready to receive download requests. In daemon mode it doesn't print any -messages to console since it runs in background. - -When the server is running it is possible to queue up downloads. This can be -done either in terminal with "nzbget -A " or by uploading -a nzb-file into server's monitor-directory (/nzb by default). - -To check the status of server start client and connect it to server: - - nzbget -C - -The client have three different (display) outputmodes, which you can select -in configuration file (on client computer) or in command line. Try them: - - nzbget -o outputmode=log -C - - nzbget -o outputmode=color -C - - nzbget -o outputmode=curses -C - -To list files in server's queue: - - nzbget -L - -It prints something like: - - [1] nzbname\filename1.rar (50.00 MB) - [2] nzbname\filename1.r01 (50.00 MB) - [3] another-nzb\filename3.r01 (100.00 MB) - [4] another-nzb\filename3.r02 (100.00 MB) - -This is the list of individual files listed within nzb-file. To print -the list of nzb-files (without content) add G-modifier to the list command: - - [1] nzbname (4.56 GB) - [2] another-nzb (4.20 GB) - -The numbers in square braces are ID's of files or groups in queue. -They can be used in edit-command. For example to move file with -ID 2 to the top of queue: - - nzbget -E T 2 - -or to pause files with IDs from 10 to 20: - - nzbget -E P 10-20 - -or to delete files from queue: - - nzbget -E D 3 10-15 20-21 16 - -The edit-command has also a group-mode which affects all files from the -same nzb-file. You need to pass an ID of the group. For example to delete -the whole group 1: - - nzbget -E G D 1 - -The switch "o" is useful to override options in configuration files. -For example: - - nzbget -o reloadqueue=no -o dupecheck=no -o parcheck=yes -s - -or: - - nzbget -o createlog=no -C - -### Running client & server on seperate machines: -------------------------------------------------- - -Since nzbget communicates via TCP/IP it's possible to have a server running on -one computer and adding downloads via a client on another computer. - -Do this by setting the "ControlIP" option in the nzbget.conf file to point to the -IP of the server (default is localhost which means client and server runs on -same computer) - -### Security warning --------------------- - -NZBGet communicates via unsecured socket connections. This makes it vulnerable. -Although server checks the password passed by client, this password is still -transmitted in unsecured way. For this reason it is highly recommended -to configure your Firewall to not expose the port used by NZBGet to WAN. - -If you need to control server from WAN it is better to connect to server's -terminal via SSH (POSIX) or remote desktop (Windows) and then run -nzbget-client-commands in this terminal. - -### Post processing scripts ---------------------------- - -After the download of nzb-file is completed nzbget can call post-processing -scripts, defined in configuration file. - -Example post-processing scripts are provided in directory "scripts". - -To use the scripts copy them into your local directory and set options -, and . - -For information on writing your own post-processing scripts please -visit NZBGet web site. - -### Web-interface ------------------ - -NZBGet has a built-in web-server providing the access to the program -functions via web-interface. - -To activate web-interface set the option "WebDir" to the path with -web-interface files. If you install using "make install-conf" as -described above the option is set automatically. If you install using -binary files you should check if the option is set correctly. - -To access web-interface from your web-browser use the server address -and port defined in NZBGet configuration file in options "ControlIP" and -"ControlPort". For example: - - http://localhost:6789/ - -For login credentials type username and the password defined by -options "ControlUsername" (default "nzbget") and "ControlPassword" -(default "tegbzn6789"). - -In a case your browser forget credentials, to prevent typing them each -time, there is a workaround - use URL in the form: - - http://localhost:6789/username:password/ - -Please note, that in this case the password is saved in a bookmark or in -browser history in plain text and is easy to find by persons having -access to your computer. - -## 8. Authors - -NZBGet is developed and maintained by Andrey Prygunkov -(hugbug@users.sourceforge.net). - -The original project was initially created by Sven Henkel -(sidddy@users.sourceforge.net) in 2004 and later developed by -Bo Cordes Petersen (placebodk@users.sourceforge.net) until 2005. -In 2007 the abandoned project was overtaken by Andrey Prygunkov. -Since then the program has been completely rewritten. - -NZBGet distribution archive includes additional components -written by other authors: - -Par2: - Peter Brian Clements - -Par2 library API: - Francois Lesueur - -jQuery: - John Resig - The Dojo Foundation - -Bootstrap: - Twitter, Inc - -Raphaël: - Dmitry Baranovskiy - Sencha Labs - -Elycharts: - Void Labs s.n.c. - -iconSweets: - Yummygum - -Boost: - Boost organization and wider Boost community - - -## 9. Copyright - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -The complete content of license is provided in file COPYING. - -Additional exemption: compiling, linking, and/or using OpenSSL is allowed. - -## 10. Contact - -If you encounter any problem, feel free to contact us - https://nzbget.com/contact/ + - [POSIX](docs/POSIX.md) + - [Windows](docs/WINDOWS.md) + - [Synology](synology/build-info.md) + - [QNAP](qnap/build-info.md) diff --git a/Makefile.am b/Makefile.am index b853f8f8a..1871a03fa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -332,7 +332,7 @@ linux_FILES = \ doc_FILES = \ INSTALLATION.md \ - ChangeLog \ + ChangeLog.md \ COPYING par2doc_FILES = \ @@ -352,6 +352,8 @@ webui_FILES = \ webui/messages.js \ webui/status.js \ webui/style.css \ + webui/light-theme.css \ + webui/dark-theme.css \ webui/upload.js \ webui/util.js \ webui/config.js \ @@ -365,8 +367,7 @@ webui_FILES = \ webui/lib/raphael.min.js \ webui/lib/elycharts.js \ webui/lib/elycharts.min.js \ - webui/img/house-16.ico \ - webui/img/download-16.ico \ + webui/lib/material-icons.woff2 \ webui/img/icons.png \ webui/img/icons-2x.png \ webui/img/transmit.gif \ diff --git a/README.md b/README.md index 6a4356d97..524d01045 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![License](https://img.shields.io/badge/license-GPL-blue.svg)](http://www.gnu.org/licenses/) ![GitHub release (by tag)](https://img.shields.io/github/downloads/nzbgetcom/nzbget/v22.0/total?label=v22.0) ![GitHub release (by tag)](https://img.shields.io/github/downloads/nzbgetcom/nzbget/v23.0/total?label=v23.0) +![GitHub release (by tag)](https://img.shields.io/github/downloads/nzbgetcom/nzbget/v24.0/total?label=v24.0) ![docker pulls](https://img.shields.io/docker/pulls/nzbgetcom/nzbget.svg) [![linux build](https://github.com/nzbgetcom/nzbget/actions/workflows/linux.yml/badge.svg?branch=main)](https://github.com/nzbgetcom/nzbget/actions/workflows/linux.yml) @@ -36,7 +37,9 @@ More information available at https://nzbget.com We provide a easy-to-use installer for each platform we support. Please download binaries from our [releases](https://github.com/nzbgetcom/nzbget/tags) page. -We also provide a docker image for popular architectures. [Docker readme](docker/README.md) +Linux DEB/RPM packages are available from [releases](https://github.com/nzbgetcom/nzbget/tags) page or from DEB/RPM [repositories](https://nzbgetcom.github.io). + +Docker images are available for x86-64 / arm64 / armv7 architectures. [Docker readme](docker/README.md) Synology packages are available as SynoCommunity packages and SPK packages. [Synology readme](synology/README.md) @@ -52,25 +55,26 @@ QNAP packages are available as native packages and buildroot packages. [QNAP rea `Windows`: Windows 7 and later, 32 or 64 Bit. -`Linux`: Linux kernel 2.6 and later, x86 (32 or 64 Bit), ARM 32-bit (armel armhf), ARM 64-bit (aarch64) +`Linux`: Linux kernel 2.6 and later, x86 (32 or 64 Bit), ARM 32-bit (armel armhf), ARM 64-bit (aarch64), MIPS (mipseb mipsel), PowerPC (ppc6xx ppc500), RISC-V 64-bit (riscv64) -`macOS`: macOS 10.13 High Sierra and later, Intel / Apple Silicon. +`macOS`: X64 binary: macOS Mojave 10.14+, Universal (Intel / Apple Silicon) binary: macOS Monterey 12+ ## Building from sources [General instructions](INSTALLATION.md) -[Linux cross-compiling / buildroot](linux/build-info.md) - -[macOS](osx/build-info.md) +## Extensions + - [V1 (NZBGet v22 and below)](docs/extensions/EXTENSIONS_LEGACY.md) + - [V2 (NZBGet v23 and above)](docs/extensions/EXTENSIONS.md) -[Synology](synology/build-info.md) +## Brief introduction on how to use NZBGet + - [How to use](docs/HOW_TO_USE.md) ## Contribution Contributions are very welcome - not only from developers, but from our users too - please don't hesitate to participate in [discussions](https://github.com/nzbgetcom/nzbget/discussions) or [create a new discussion](https://github.com/nzbgetcom/nzbget/discussions/new/choose) -For more information - see [Contributing](CONTRIBUTING.md). +For more information - see [Contributing](docs/CONTRIBUTING.md). ## Donate diff --git a/cmake/boost.cmake b/cmake/boost.cmake new file mode 100644 index 000000000..7d517368e --- /dev/null +++ b/cmake/boost.cmake @@ -0,0 +1,19 @@ +set(ROOT ${CMAKE_BINARY_DIR}/boost/) + +ExternalProject_add( + boost + PREFIX boost + URL https://github.com/boostorg/boost/releases/download/boost-1.84.0/boost-1.84.0.tar.xz + TLS_VERIFY TRUE + BUILD_IN_SOURCE TRUE + GIT_SHALLOW TRUE + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + CONFIGURE_COMMAND ${ROOT}src/boost/bootstrap.sh + --with-libraries=json + --prefix=${ROOT}build + BUILD_COMMAND ${ROOT}src/boost/b2 link=static + INSTALL_COMMAND ${ROOT}src/boost/b2 install +) + +set(LIBS ${LIBS} ${ROOT}build/lib/libboost_json.a) +set(INCLUDES ${INCLUDES} ${ROOT}build/include/) diff --git a/cmake/config.h.in b/cmake/config.h.in new file mode 100644 index 000000000..44783d29a --- /dev/null +++ b/cmake/config.h.in @@ -0,0 +1,168 @@ +/* Name of package */ +#cmakedefine PACKAGE "@PACKAGE@" + +/* Version number of package */ +#cmakedefine VERSION "@VERSION@@VERSION_SUFFIX@" + +/* Define to the address where bug reports for this package should be sent. */ +#cmakedefine PACKAGE_BUGREPORT "@PACKAGE_BUGREPORT@" + +/* Define to 1 to not use curses */ +#cmakedefine DISABLE_CURSES + +/* Define to 1 to disable gzip-support */ +#cmakedefine DISABLE_GZIP + +/* Define to 1 to not use libxml2, only for development purposes */ +#cmakedefine DISABLE_LIBXML2 + +/* Define to 1 to disable par-verification and repair */ +#cmakedefine DISABLE_PARCHECK + +/* Define to 1 to not use TLS/SSL */ +#cmakedefine DISABLE_TLS + +/* Define to 1 to install an empty signal handler for SIGCHLD */ +#cmakedefine SIGCHLD_HANDLER @SIGCHLD_HANDLER@ + +/* Define to the name of macro which returns the name of function being + compiled */ +#cmakedefine FUNCTION_MACRO_NAME @FUNCTION_MACRO_NAME@ + +/* Define to 1 to create stacktrace on segmentation faults */ +#cmakedefine HAVE_BACKTRACE @HAVE_BACKTRACE@ + +/* Define to 1 if ctime_r takes 2 arguments */ +#cmakedefine HAVE_CTIME_R_2 @HAVE_CTIME_R_2@ + +/* Define to 1 if ctime_r takes 3 arguments */ +#cmakedefine HAVE_CTIME_R_3 @HAVE_CTIME_R_3@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_CURSES_H @HAVE_CURSES_H@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ENDIAN_H @HAVE_ENDIAN_H@ + +/* Define to 1 if fdatasync is supported */ +#cmakedefine HAVE_FDATASYNC @HAVE_FDATASYNC@ + +/* Define to 1 if fseeko (and presumably ftello) exists and is declared. */ +#cmakedefine HAVE_FSEEKO @HAVE_FSEEKO@ + +/* Define to 1 if F_FULLFSYNC is supported */ +#cmakedefine HAVE_FULLFSYNC @HAVE_FULLFSYNC@ + +/* Define to 1 if getaddrinfo is supported */ +#cmakedefine HAVE_GETADDRINFO @HAVE_GETADDRINFO@ + +/* Define to 1 if gethostbyname_r is supported */ +#cmakedefine HAVE_GETHOSTBYNAME_R @HAVE_GETHOSTBYNAME_R@ + +/* Define to 1 if gethostbyname_r takes 3 arguments */ +#cmakedefine HAVE_GETHOSTBYNAME_R_3 @HAVE_GETHOSTBYNAME_R_3@ + +/* Define to 1 if gethostbyname_r takes 5 arguments */ +#cmakedefine HAVE_GETHOSTBYNAME_R_5 @HAVE_GETHOSTBYNAME_R_5@ + +/* Define to 1 if gethostbyname_r takes 6 arguments */ +#cmakedefine HAVE_GETHOSTBYNAME_R_6 @HAVE_GETHOSTBYNAME_R_6@ + +/* Define to 1 if you have the `getopt' function. */ +#cmakedefine HAVE_GETOPT @HAVE_GETOPT@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_GETOPT_H @HAVE_GETOPT_H@ + +/* Define to 1 if getopt_long is supported */ +#cmakedefine HAVE_GETOPT_LONG @HAVE_GETOPT_LONG@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_INTTYPES_H @HAVE_INTTYPES_H@ + +/* Define to 1 to use GnuTLS library for TLS/SSL-support. */ +#cmakedefine HAVE_LIBGNUTLS @HAVE_LIBGNUTLS@ + +/* Define to 1 if lockf is supported */ +#cmakedefine HAVE_LOCKF @HAVE_LOCKF@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NCURSES_H @HAVE_NCURSES_H@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NCURSES_NCURSES_H @HAVE_NCURSES_NCURSES_H@ + +/* Define to 1 to use Nettle library for decryption. */ +#cmakedefine HAVE_NETTLE @HAVE_NETTLE@ + +/* Define to 1 to use OpenSSL library for TLS/SSL-support and decryption. */ +#cmakedefine HAVE_OPENSSL @HAVE_OPENSSL@ + +/* Define to 1 if pthread_cancel is supported */ +#cmakedefine HAVE_PTHREAD_CANCEL @HAVE_PTHREAD_CANCEL@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_REGEX_H @HAVE_REGEX_H@ + +/* Define to 1 if _SC_NPROCESSORS_ONLN is present in unistd.h */ +#cmakedefine HAVE_SC_NPROCESSORS_ONLN @HAVE_SC_NPROCESSORS_ONLN@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDINT_H @HAVE_STDINT_H@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDIO_H @HAVE_STDIO_H@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDLIB_H @HAVE_STDLIB_H@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRINGS_H @HAVE_STRINGS_H@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRING_H @HAVE_STRING_H@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_PRCTL_H @HAVE_SYS_PRCTL_H@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_STAT_H @HAVE_SYS_STAT_H@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TYPES_H @HAVE_SYS_TYPES_H@ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H @HAVE_UNISTD_H@ + +/* Define to 1 if variadic macros are supported */ +#cmakedefine HAVE_VARIADIC_MACROS @HAVE_VARIADIC_MACROS@ + +/* Define to 1 if OpenSSL supports function "X509_check_host" */ +#cmakedefine HAVE_X509_CHECK_HOST @HAVE_X509_CHECK_HOST@ + +/* Determine what socket length (socklen_t) data type is */ +#cmakedefine SOCKLEN_T @SOCKLEN_T@ + +/* Number of bits in a file offset, on hosts where this is settable. */ +#cmakedefine _FILE_OFFSET_BITS @_FILE_OFFSET_BITS@ + +/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */ +#cmakedefine _LARGEFILE_SOURCE @_LARGEFILE_SOURCE@ + +/* Define for large files, on AIX-style hosts. */ +#cmakedefine _LARGE_FILES @_LARGE_FILES@ + +/* Define to `unsigned int' if does not define. */ +#cmakedefine size_t @size_t@ + +/* Define if AMD64 */ +#cmakedefine __amd64__ + +/* Define if i686 */ +#cmakedefine __i686__ + +/* Use 32BIT TIME_T */ +#cmakedefine _USE_32BIT_TIME_T + +/* detection of memory leaks */ +#cmakedefine _CRTDBG_MAP_ALLOC diff --git a/cmake/install.cmake b/cmake/install.cmake new file mode 100644 index 000000000..f34c1ab31 --- /dev/null +++ b/cmake/install.cmake @@ -0,0 +1,43 @@ +set(DOC_FILES + ${CMAKE_SOURCE_DIR}/ChangeLog.md + ${CMAKE_SOURCE_DIR}/COPYING +) +set(SHARE_DIR ${CMAKE_INSTALL_PREFIX}/share/${PACKAGE}) +set(CONF_FILE ${CMAKE_SOURCE_DIR}/nzbget.conf) +set(WEBUI_DIR ${CMAKE_SOURCE_DIR}/webui) +set(DOC_FILES_DEST ${SHARE_DIR}/doc) +set(CONF_FILE_DEST ${SHARE_DIR}) +set(WEBUI_DIR_DEST ${SHARE_DIR}) +set(BIN_FILE_DEST ${CMAKE_INSTALL_PREFIX}/bin) + +install(TARGETS ${PACKAGE} PERMISSIONS + OWNER_EXECUTE + OWNER_WRITE + OWNER_READ + GROUP_READ + GROUP_EXECUTE + WORLD_READ + WORLD_EXECUTE + DESTINATION ${BIN_FILE_DEST}) +install(FILES ${DOC_FILES} DESTINATION ${DOC_FILES_DEST}) +install(DIRECTORY ${WEBUI_DIR} DESTINATION ${WEBUI_DIR_DEST}) + +file(READ ${CONF_FILE} CONFIG_CONTENT) +string(REPLACE "WebDir=" "WebDir=${WEBUI_DIR_DEST}/webui" MODIFIED_CONFIG_CONTENT "${CONFIG_CONTENT}") +string(REPLACE "ConfigTemplate=" "ConfigTemplate=${CONF_FILE_DEST}/nzbget.conf" MODIFIED_CONFIG_CONTENT "${MODIFIED_CONFIG_CONTENT}") +file(WRITE ${CMAKE_BINARY_DIR}/nzbget.conf "${MODIFIED_CONFIG_CONTENT}") +install(FILES ${CMAKE_BINARY_DIR}/nzbget.conf DESTINATION ${CONF_FILE_DEST}) + +if(NOT EXISTS ${CMAKE_INSTALL_PREFIX}/etc/nzbget.conf) + install(FILES ${CMAKE_BINARY_DIR}/nzbget.conf DESTINATION ${CMAKE_INSTALL_PREFIX}/etc) +else() + message(STATUS "nzbget.conf is already installed in ${CMAKE_INSTALL_PREFIX}/etc") + message(STATUS "If you want to overwrite it, then do it manually with caution") +endif() + +add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -E remove_directory ${DOC_FILES_DEST} + COMMAND ${CMAKE_COMMAND} -E remove_directory ${SHARE_DIR} + COMMAND ${CMAKE_COMMAND} -E remove ${BIN_FILE_DEST}/${PACKAGE} + COMMENT "Uninstalling" ${PACKAGE} +) diff --git a/cmake/posix.cmake b/cmake/posix.cmake new file mode 100644 index 000000000..ff46d04e2 --- /dev/null +++ b/cmake/posix.cmake @@ -0,0 +1,339 @@ +option(ENABLE_STATIC "Build static executable") +option(ENABLE_TESTS "Build tests") +option(ENABLE_CLANG_TIDY "Enable Clang-Tidy static analizer") +option(DISABLE_TLS "Disable TLS") +option(DISABLE_CURSES "Disable curses") +option(DISABLE_GZIP "Disable gzip") +option(DISABLE_PARCHECK "Disable parcheck") +option(USE_OPENSSL "Use OpenSSL" ON) +option(USE_GNUTLS "Use GnuTLS" OFF) + +if(NOT DISABLE_TLS AND USE_GNUTLS) + set(USE_OPENSSL OFF) +endif() + +if(DISABLE_TLS) + set(USE_GNUTLS OFF) + set(USE_OPENSSL OFF) +endif() + +message(STATUS "TOOLCHAIN OPTIONS:") +message(STATUS " SYSTEM NAME ${CMAKE_SYSTEM_NAME}") +message(STATUS " SYSTEM PROCESSOR ${CMAKE_SYSTEM_PROCESSOR}") +message(STATUS "BUILD OPTIONS:") +message(STATUS " BUILD TYPE: ${CMAKE_BUILD_TYPE}") +message(STATUS " ENABLE STATIC: ${ENABLE_STATIC}") +message(STATUS " ENABLE TESTS: ${ENABLE_TESTS}") +message(STATUS " DISABLE TLS: ${DISABLE_TLS}") +message(STATUS " - OPENSSL: ${USE_OPENSSL}") +message(STATUS " - GNUTLS: ${USE_GNUTLS}") +message(STATUS " DISABLE CURSES: ${DISABLE_CURSES}") +message(STATUS " DISABLE GZIP: ${DISABLE_GZIP}") +message(STATUS " DISABLE PARCHECK: ${DISABLE_PARCHECK}") + +if(ENABLE_CLANG_TIDY) + set(CMAKE_CXX_CLANG_TIDY clang-tidy -checks=-*,readability-*) +endif() + +if(ENABLE_STATIC) + # due to the error "ld: library not found for -crt0.o" when using Apple Clang with "-static" + if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + set(CMAKE_EXE_LINKER_FLAGS_DEBUG "-static" CACHE STRING "" FORCE) + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -static" CACHE STRING "" FORCE) + endif() + set(BUILD_SHARED_LIBS OFF) + set(LIBS $ENV{LIBS}) + set(INCLUDES $ENV{INCLUDES}) + include(${CMAKE_SOURCE_DIR}/lib/sources.cmake) +else() + find_package(Threads REQUIRED) + find_package(LibXml2 REQUIRED) + + set(LIBS ${LIBS} Threads::Threads LibXml2::LibXml2) + set(INCLUDES ${INCLUDES} ${LIBXML2_INCLUDE_DIR}) + + if(NOT DISABLE_TLS) + if(USE_OPENSSL AND NOT USE_GNUTLS) + find_package(OpenSSL REQUIRED) + set(LIBS ${LIBS} OpenSSL::SSL OpenSSL::Crypto) + set(INCLUDES ${INCLUDES} ${OPENSSL_INCLUDE_DIR}) + elseif(USE_GNUTLS) + find_package(GnuTLS REQUIRED) + find_library(NETTLE_LIBRARY NAMES nettle libnettle) + set(INCLUDES ${INCLUDES} ${GNUTLS_INCLUDE_DIRS}) + set(LIBS ${LIBS} GnuTLS::GnuTLS ${NETTLE_LIBRARY}) + endif() + endif() + + if(NOT DISABLE_CURSES) + set(CURSES_NEED_NCURSES TRUE) + find_package(Curses REQUIRED) + set(INCLUDES ${INCLUDES} ${CURSES_INCLUDE_DIRS}) + set(LIBS ${LIBS} ${CURSES_LIBRARIES}) + endif() + + if(NOT DISABLE_GZIP) + find_package(ZLIB REQUIRED) + set(INCLUDES ${INCLUDES} ${ZLIB_INCLUDE_DIRS}) + set(LIBS ${LIBS} ZLIB::ZLIB) + endif() + + find_package(Boost COMPONENTS json) + + if(NOT Boost_JSON_FOUND) + message(STATUS "The Boost library will be installed from github") + include(ExternalProject) + + include(${CMAKE_SOURCE_DIR}/cmake/boost.cmake) + + include(${CMAKE_SOURCE_DIR}/lib/sources.cmake) + + add_dependencies(${PACKAGE} boost) + add_dependencies(yencode boost) + add_dependencies(par2 boost) + add_dependencies(regex boost) + else() + set(LIBS ${LIBS} Boost::json) + set(INCLUDES ${INCLUDES} ${Boost_INCLUDE_DIR}) + + include(${CMAKE_SOURCE_DIR}/lib/sources.cmake) + endif() +endif() + +include(CheckIncludeFiles) +include(CheckLibraryExists) +include(CheckSymbolExists) +include(CheckFunctionExists) +include(CheckTypeSize) +include(CheckCSourceCompiles) +include(CheckCXXSourceCompiles) + +check_include_files(sys/prctl.h HAVE_SYS_PRCTL_H) +check_include_files(regex.h HAVE_REGEX_H) +check_include_files(endian.h HAVE_ENDIAN_H) +check_include_files(getopt.h HAVE_GETOPT_H) +check_include_file(inttypes.h HAVE_INTTYPES_H) +check_include_file(stdint.h HAVE_STDINT_H) +check_include_file(stdio.h HAVE_STDIO_H) +check_include_file(stdlib.h HAVE_STDLIB_H) +check_include_file(strings.h HAVE_STRINGS_H) +check_include_file(string.h HAVE_STRING_H) +check_include_file(sys/stat.h HAVE_SYS_STAT_H) +check_include_file(unistd.h HAVE_UNISTD_H) + +check_library_exists(pthread pthread_create "" HAVE_PTHREAD_CREATE) +check_library_exists(socket socket "" HAVE_SOCKET) +check_library_exists(nsl inet_addr "" HAVE_INET_ADDR) +check_library_exists(resolv hstrerror "" HAVE_HSTRERROR) + +check_symbol_exists(lockf unistd.h HAVE_LOCKF) +check_symbol_exists(pthread_cancel pthread.h HAVE_PTHREAD_CANCEL) +check_symbol_exists(F_FULLFSYNC fcntl.h HAVE_FULLFSYNC) + +check_function_exists(getopt_long HAVE_GETOPT_LONG) +check_function_exists(fdatasync HAVE_FDATASYNC) + +set(SIGCHLD_HANDLER 1) + +if(USE_OPENSSL) + set(HAVE_OPENSSL 1) +endif() + +if(USE_GNUTLS) + set(HAVE_LIBGNUTLS 1) + set(HAVE_NETTLE 1) +endif() + +if(NOT DISABLE_CURSES) + set(HAVE_NCURSES_H 1) +endif() + +if(NOT DISABLED_PARCHECK) + check_type_size(size_t SIZE_T) + check_symbol_exists(fseeko "stdio.h" HAVE_FSEEKO) + check_function_exists(getopt HAVE_GETOPT) +else() + set(DISABLED_PARCHECK 1) +endif() + +# check ctime_r +check_cxx_source_compiles(" + #include + int main() + { + time_t clock; + char buf[26]; + ctime_r(&clock, buf, 26); + return 0; + }" HAVE_CTIME_R_3) + +if(NOT HAVE_CTIME_R_3) + check_cxx_source_compiles(" + #include + int main() + { + time_t clock; + char buf[26]; + ctime_r(&clock, buf); + return 0; + }" HAVE_CTIME_R_2) +endif() +if(NOT HAVE_CTIME_R_2) + message(FATAL_ERROR "ctime_r function not found") +endif() + +check_function_exists(getaddrinfo HAVE_GETADDRINFO) +if(NOT HAVE_GETADDRINFO) + check_library_exists(nsl getaddrinfo "" HAVE_GETADDRINFO) +endif() + +# check gethostbyname_r, if getaddrinfo is not available +if(NOT HAVE_GETADDRINFO) + check_cxx_source_compiles(" + #include + int main() + { + char* szHost; + struct hostent hinfobuf; + char* strbuf; + int h_errnop; + struct hostent* hinfo = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &h_errnop); + return 0; + }" HAVE_GETHOSTBYNAME_R_5) +if(NOT HAVE_GETHOSTBYNAME_R_5) + check_cxx_source_compiles(" + #include + int main() + { + char* szHost; + struct hostent* hinfo; + struct hostent hinfobuf; + char* strbuf; + int h_errnop; + int err = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &hinfo, &h_errnop); + return 0; + }" HAVE_GETHOSTBYNAME_R_6) +if(NOT HAVE_GETHOSTBYNAME_R_6) + check_cxx_source_compiles(" + #include + int main() + { + char* szHost; + struct hostent hinfo; + struct hostent_data hinfobuf; + int err = gethostbyname_r(szHost, &hinfo, &hinfobuf); + return 0; + }" HAVE_GETHOSTBYNAME_R_3) +if(NOT HAVE_GETHOSTBYNAME_R_3) + message(FATAL_ERROR "gethostbyname_r function not found") +endif() +endif() +endif() +if (NOT HAVE_GETHOSTBYNAME_R_3) + set(HAVE_GETHOSTBYNAME_R 1) + check_library_exists(nsl gethostbyname_r "" HAVE_GETHOSTBYNAME_R) +endif() +endif() + +# Determine what socket length (socklen_t) data type is +check_cxx_source_compiles(" + #include + #include + #include + int main() + { + (void)getsockopt (1, 1, 1, NULL, (socklen_t*)NULL); + }" SOCKLEN) +if(SOCKLEN) + set(SOCKLEN_T socklen_t) +else() + check_cxx_source_compiles(" + #include + #include + #include + int main() + { + (void)getsockopt (1, 1, 1, NULL, (size_t*)NULL); + }" SOCKLEN) +if(SOCKLEN) + set(SOCKLEN_T size_t) +else() + check_cxx_source_compiles(" + #include + #include + #include + int main() + { + (void)getsockopt (1, 1, 1, NULL, (int*)NULL); + }" SOCKLEN) +if(SOCKLEN) + set(SOCKLEN_T int) +else() + set(SOCKLEN_T int) +endif() +endif() +endif() + +# Check CPU cores via sysconf +check_cxx_source_compiles(" + #include + int main() + { + int a = _SC_NPROCESSORS_ONLN; + }" HAVE_SC_NPROCESSORS_ONLN) + +# Check TLS/SSL +if(USE_OPENSSL AND NOT ENABLE_STATIC) + check_library_exists(OpenSSL::Crypto X509_check_host "" HAVE_X509_CHECK_HOST) +elseif(USE_OPENSSL AND ENABLE_STATIC) + set(HAVE_X509_CHECK_HOST 1) +endif() + +check_cxx_source_compiles(" + #include + int main() + { + printf(\"%s\", __FUNCTION__); + return 0; + }" FUNCTION_MACRO_NAME_ONE) + +check_cxx_source_compiles(" + #include + int main() + { + printf(\"%s\", __func__); + return 0; + }" FUNCTION_MACRO_NAME_TWO) + +if(FUNCTION_MACRO_NAME_ONE) + set(FUNCTION_MACRO_NAME __FUNCTION__) +elseif (FUNCTION_MACRO_NAME_TWO) + set(FUNCTION_MACRO_NAME __func__) +endif() + +check_cxx_source_compiles(" + #define macro(...) macrofunc(__VA_ARGS__) + int macrofunc(int a, int b) { return a + b; } + int main() + { + int a = macro(1, 2); + return 0; + }" HAVE_VARIADIC_MACROS) + +if(HAVE_VARIADIC_MACROS) + set(DHAVE_VARIADIC_MACROS 1) +endif() + +check_cxx_source_compiles(" + #include + #include + #include + int main() + { + void* array[100]; + size_t size; + char** strings; + size = backtrace(array, 100); + strings = backtrace_symbols(array, size); + return 0; + }" HAVE_BACKTRACE) diff --git a/cmake/toolchain.cmake b/cmake/toolchain.cmake new file mode 100644 index 000000000..329786886 --- /dev/null +++ b/cmake/toolchain.cmake @@ -0,0 +1,19 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR ${ARCH}) + +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +set(CMAKE_AR ${TOOLCHAIN_PREFIX}-ar) +set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}-as) +set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc) +set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++) +set(CMAKE_LINKER ${TOOLCHAIN_PREFIX}-ld) +set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}-objcopy) +set(CMAKE_RANLIB ${TOOLCHAIN_PREFIX}-ranlib) +set(CMAKE_SIZE ${TOOLCHAIN_PREFIX}-size) +set(CMAKE_STRIP ${TOOLCHAIN_PREFIX}-strip) + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/cmake/windows.cmake b/cmake/windows.cmake new file mode 100644 index 000000000..bfc265bd7 --- /dev/null +++ b/cmake/windows.cmake @@ -0,0 +1,57 @@ +option(ENABLE_TESTS "Enable tests") +option(DISABLE_TLS "Disable TLS") + +message(STATUS "TOOLCHAIN OPTIONS:") +message(STATUS " SYSTEM NAME ${CMAKE_SYSTEM_NAME}") +message(STATUS " SYSTEM PROCESSOR ${CMAKE_SYSTEM_PROCESSOR}") +message(STATUS " TARGET TRIPLET ${VCPKG_TARGET_TRIPLET}") +message(STATUS "BUILD OPTIONS:") +message(STATUS " BUILD TYPE: ${CMAKE_BUILD_TYPE}") +message(STATUS " ENABLE TESTS: ${ENABLE_TESTS}") +message(STATUS " DISABLE TLS: ${DISABLE_TLS}") + +set(Boost_USE_STATIC_LIBS ON) + +find_package(Threads REQUIRED) +find_package(LibXml2 REQUIRED) +find_package(Boost REQUIRED COMPONENTS json) + +set(LIBS Threads::Threads Boost::json LibXml2::LibXml2) +set(INCLUDES ${Boost_INCLUDE_DIR} ${LIBXML2_INCLUDE_DIR}) + +if(NOT DISABLE_TLS) + find_package(OpenSSL REQUIRED) + set(HAVE_OPENSSL 1) + set(HAVE_X509_CHECK_HOST 1) + set(LIBS ${LIBS} OpenSSL::SSL OpenSSL::Crypto) + set(INCLUDES ${INCLUDES} ${OPENSSL_INCLUDE_DIR}) +endif() + +find_package(ZLIB REQUIRED) +set(LIBS ${LIBS} ZLIB::ZLIB) +set(INCLUDES ${INCLUDES} ${ZLIB_INCLUDE_DIRS}) + +include(${CMAKE_SOURCE_DIR}/lib/sources.cmake) +set(INCLUDES ${INCLUDES} + ${CMAKE_SOURCE_DIR}/daemon/windows + ${CMAKE_SOURCE_DIR}/windows/resources +) + +set(FUNCTION_MACRO_NAME __FUNCTION__) +set(HAVE_CTIME_R_3 1) +set(HAVE_VARIADIC_MACROS 1) +set(HAVE_GETADDRINFO 1) +set(SOCKLEN_T socklen_t) +set(HAVE_REGEX_H 1) +set(HAVE_STDINT_H 1) + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(__amd64__ 1) +else() + set(__i686__ 1) + set(_USE_32BIT_TIME_T 1) +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(_CRTDBG_MAP_ALLOC 1) +endif() diff --git a/cmake_config.h.in b/cmake_config.h.in deleted file mode 100644 index aa35cd479..000000000 --- a/cmake_config.h.in +++ /dev/null @@ -1,5 +0,0 @@ -/* Name of package */ -#cmakedefine PACKAGE "@PACKAGE@" - -/* Version number of package */ -#cmakedefine VERSION "@VERSION@" diff --git a/configure.ac b/configure.ac index b4a8ca9c9..5966bc22d 100644 --- a/configure.ac +++ b/configure.ac @@ -21,7 +21,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.65) -AC_INIT(nzbget, 23.0, https://github.com/nzbgetcom/nzbget/issues) +AC_INIT(nzbget, 24.0, https://github.com/nzbgetcom/nzbget/issues) AC_CONFIG_AUX_DIR(posix) AC_CANONICAL_TARGET AM_INIT_AUTOMAKE([foreign subdir-objects]) diff --git a/daemon/extension/Extension.cpp b/daemon/extension/Extension.cpp index 044fbad22..ae81be81c 100644 --- a/daemon/extension/Extension.cpp +++ b/daemon/extension/Extension.cpp @@ -1,7 +1,7 @@ /* * This file is part of nzbget. See . * - * Copyright (C) 2023 Denis + * Copyright (C) 2023-2024 Denis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -79,11 +79,21 @@ namespace Extension m_version = std::move(version); } + void Script::SetNzbgetMinVersion(std::string version) + { + m_nzbgetMinVersion = std::move(version); + } + const char* Script::GetVersion() const { return m_version.c_str(); } + const char* Script::GetNzbgetMinVersion() const + { + return m_nzbgetMinVersion.c_str(); + } + void Script::SetLicense(std::string license) { m_license = std::move(license); @@ -230,6 +240,7 @@ namespace Extension json["Homepage"] = script.GetHomepage(); json["License"] = script.GetLicense(); json["Version"] = script.GetVersion(); + json["NZBGetMinVersion"] = script.GetNzbgetMinVersion(); json["PostScript"] = script.GetPostScript(); json["ScanScript"] = script.GetScanScript(); json["QueueScript"] = script.GetQueueScript(); @@ -256,6 +267,9 @@ namespace Extension optionJson["Name"] = option.name; optionJson["DisplayName"] = option.displayName; + optionJson["Section"] = option.section.name; + optionJson["Multi"] = option.section.multi; + optionJson["Prefix"] = option.section.prefix; if (const std::string* val = boost::variant2::get_if(&option.value)) { @@ -296,7 +310,9 @@ namespace Extension commandJson["Name"] = command.name; commandJson["DisplayName"] = command.displayName; commandJson["Action"] = command.action; - + commandJson["Section"] = command.section.name; + commandJson["Multi"] = command.section.multi; + commandJson["Prefix"] = command.section.prefix; for (const auto& line : command.description) { @@ -330,6 +346,7 @@ namespace Extension AddNewNode(structNode, "Homepage", "string", script.GetHomepage()); AddNewNode(structNode, "License", "string", script.GetLicense()); AddNewNode(structNode, "Version", "string", script.GetVersion()); + AddNewNode(structNode, "NZBGetMinVersion", "string", script.GetNzbgetMinVersion()); AddNewNode(structNode, "PostScript", "boolean", BoolToStr(script.GetPostScript())); AddNewNode(structNode, "ScanScript", "boolean", BoolToStr(script.GetScanScript())); @@ -358,6 +375,9 @@ namespace Extension AddNewNode(commandsNode, "Name", "string", command.name.c_str()); AddNewNode(commandsNode, "DisplayName", "string", command.displayName.c_str()); AddNewNode(commandsNode, "Action", "string", command.action.c_str()); + AddNewNode(commandsNode, "Multi", "boolean", BoolToStr(command.section.multi)); + AddNewNode(commandsNode, "Section", "string", command.section.name.c_str()); + AddNewNode(commandsNode, "Prefix", "string", command.section.prefix.c_str()); xmlNodePtr descriptionNode = xmlNewNode(NULL, BAD_CAST "Description"); for (const std::string& line : command.description) @@ -372,6 +392,9 @@ namespace Extension { AddNewNode(optionsNode, "Name", "string", option.name.c_str()); AddNewNode(optionsNode, "DisplayName", "string", option.displayName.c_str()); + AddNewNode(optionsNode, "Multi", "boolean", BoolToStr(option.section.multi)); + AddNewNode(optionsNode, "Section", "string", option.section.name.c_str()); + AddNewNode(optionsNode, "Prefix", "string", option.section.prefix.c_str()); if (const std::string* val = boost::variant2::get_if(&option.value)) { @@ -416,21 +439,18 @@ namespace Extension return result; } - namespace + void AddNewNode(xmlNodePtr rootNode, const char* name, const char* type, const char* value) { - void AddNewNode(xmlNodePtr rootNode, const char* name, const char* type, const char* value) - { - xmlNodePtr memberNode = xmlNewNode(NULL, BAD_CAST "member"); - xmlNodePtr valueNode = xmlNewNode(NULL, BAD_CAST "value"); - xmlNewChild(memberNode, NULL, BAD_CAST "name", BAD_CAST name); - xmlNewChild(valueNode, NULL, BAD_CAST type, BAD_CAST value); - xmlAddChild(memberNode, valueNode); - xmlAddChild(rootNode, memberNode); - } + xmlNodePtr memberNode = xmlNewNode(NULL, BAD_CAST "member"); + xmlNodePtr valueNode = xmlNewNode(NULL, BAD_CAST "value"); + xmlNewChild(memberNode, NULL, BAD_CAST "name", BAD_CAST name); + xmlNewChild(valueNode, NULL, BAD_CAST type, BAD_CAST value); + xmlAddChild(memberNode, valueNode); + xmlAddChild(rootNode, memberNode); + } - const char* BoolToStr(bool value) - { - return value ? "true" : "false"; - } + const char* BoolToStr(bool value) + { + return value ? "true" : "false"; } } diff --git a/daemon/extension/Extension.h b/daemon/extension/Extension.h index 6e3e7ef11..85cc504d2 100644 --- a/daemon/extension/Extension.h +++ b/daemon/extension/Extension.h @@ -1,7 +1,7 @@ /* * This file is part of nzbget. See . * - * Copyright (C) 2023 Denis + * Copyright (C) 2023-2024 Denis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,7 +33,7 @@ namespace Extension bool feed = false; }; - class Script + class Script final { public: Script() = default; @@ -54,7 +54,9 @@ namespace Extension void SetAuthor(std::string author); const char* GetAuthor() const; void SetVersion(std::string version); + void SetNzbgetMinVersion(std::string version); const char* GetVersion() const; + const char* GetNzbgetMinVersion() const; void SetLicense(std::string license); const char* GetLicense() const; void SetHomepage(std::string homepage); @@ -91,6 +93,7 @@ namespace Extension std::string m_rootDir; std::string m_author; std::string m_version; + std::string m_nzbgetMinVersion; std::string m_homepage; std::string m_license; std::string m_name; @@ -107,11 +110,8 @@ namespace Extension std::string ToJsonStr(const Script& script); std::string ToXmlStr(const Script& script); - namespace - { - void AddNewNode(xmlNodePtr rootNode, const char* name, const char* type, const char* value); - const char* BoolToStr(bool value); - } + void AddNewNode(xmlNodePtr rootNode, const char* name, const char* type, const char* value); + const char* BoolToStr(bool value); } #endif diff --git a/daemon/extension/ExtensionLoader.cpp b/daemon/extension/ExtensionLoader.cpp index c5135f7a4..2ed93337d 100644 --- a/daemon/extension/ExtensionLoader.cpp +++ b/daemon/extension/ExtensionLoader.cpp @@ -1,7 +1,7 @@ /* * This file is part of nzbget. See . * - * Copyright (C) 2023 Denis + * Copyright (C) 2023-2024 Denis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,6 +27,7 @@ namespace ExtensionLoader { + const char* DEFAULT_SECTION_NAME = "options"; const char* BEGIN_SCRIPT_SIGNATURE = "### NZBGET "; const char* BEGIN_SCRIPT_COMMANDS_AND_OTPIONS = "### OPTIONS"; const char* POST_SCRIPT_SIGNATURE = "POST-PROCESSING"; @@ -83,10 +84,12 @@ namespace ExtensionLoader { continue; } + if (!inBeforeConfig && !strncmp(line.c_str(), DEFINITION_SIGNATURE, DEFINITION_SIGNATURE_LEN)) { inBeforeConfig = true; } + if (!inBeforeConfig && !inConfig) { continue; @@ -149,6 +152,7 @@ namespace ExtensionLoader continue; } + // if "OPTIONS" and other sections, e.g.: ### OPTIONS or ### CATEGORIES if (!strncmp(line.c_str(), BEGIN_SCRIPT_COMMANDS_AND_OTPIONS, BEGIN_SCRIPT_COMMANDS_AND_OTPIONS_LEN)) { ParseOptionsAndCommands(file, options, commands); @@ -181,244 +185,248 @@ namespace ExtensionLoader return true; } - namespace + + void RemoveTailAndTrim(std::string& str, const char* tail) { - void RemoveTailAndTrim(std::string& str, const char* tail) + size_t tailIdx = str.find(tail); + if (tailIdx != std::string::npos) { - size_t tailIdx = str.find(tail); - if (tailIdx != std::string::npos) - { - str.erase(tailIdx); - } - Util::TrimRight(str); + str.erase(tailIdx); } + Util::TrimRight(str); + } - void BuildDisplayName(Extension::Script& script) - { - BString<1024> shortName = script.GetName(); - if (char* ext = strrchr(shortName, '.')) *ext = '\0'; // strip file extension + void BuildDisplayName(Extension::Script& script) + { + BString<1024> shortName = script.GetName(); + if (char* ext = strrchr(shortName, '.')) *ext = '\0'; // strip file extension - const char* displayName = FileSystem::BaseFileName(shortName); + const char* displayName = FileSystem::BaseFileName(shortName); - script.SetDisplayName(displayName); - } + script.SetDisplayName(displayName); + } - void ParseOptionsAndCommands( - std::ifstream& file, - std::vector& options, - std::vector& commands) + void ParseOptionsAndCommands( + std::ifstream& file, + std::vector& options, + std::vector& commands) + { + std::vector selectOpts; + std::vector description; + std::string currSectionName = DEFAULT_SECTION_NAME; + + std::string line; + while (std::getline(file, line)) { - std::vector selectOpts; - std::vector description; + if (strstr(line.c_str(), END_SCRIPT_SIGNATURE)) + { + break; + } + + if (line.empty()) + { + continue; + } + + if (!strncmp(line.c_str(), DEFINITION_SIGNATURE, DEFINITION_SIGNATURE_LEN)) + { + currSectionName = line.substr(DEFINITION_SIGNATURE_LEN + 1); + RemoveTailAndTrim(currSectionName, "###"); + continue; + } + + size_t selectStartIdx = line.rfind("("); + size_t selectEndIdx = line.rfind(")"); + bool hasSelectOptions = description.empty() + && !strncmp(line.c_str(), "# ", 2) + && selectStartIdx != std::string::npos + && selectEndIdx != std::string::npos + && selectEndIdx == line.length() - 2; - std::string line; - while (std::getline(file, line)) + // e.g. # Description (Always, OnFailure) or # Description (1-65535) or # Description (1, 2-5, 10). + if (hasSelectOptions) { - if (strstr(line.c_str(), END_SCRIPT_SIGNATURE)) + std::string selectOptionsStr = line.substr(selectStartIdx + 1, selectEndIdx - selectStartIdx - 1); + auto result = ExtractElements(selectOptionsStr); + auto& selectOptions = result.first; + auto& delimiter = result.second; + bool canBeNum = delimiter == "-"; + + if (delimiter.empty()) // doesn't have { - break; + description.push_back(line.substr(2)); + continue; } - if (line.empty()) + if (canBeNum) { - continue; + description.push_back(line.substr(2)); + } + else + { + description.push_back(line.substr(2, selectStartIdx - 3) + "."); } - size_t selectStartIdx = line.rfind("("); - size_t selectEndIdx = line.rfind(")"); - bool hasSelectOptions = selectStartIdx != std::string::npos - && description.empty() - && selectEndIdx != std::string::npos - && selectEndIdx != std::string::npos - && !strncmp(line.c_str(), "# ", 2); + selectOpts = GetSelectOptions(selectOptions, canBeNum); + continue; + } - // e.g. # Description (Always, OnFailure) or # Description (1-65535) or # Description (1, 2-5, 10). - if (hasSelectOptions) - { - std::string selectOptionsStr = line.substr(selectStartIdx + 1, selectEndIdx - selectStartIdx - 1); - auto result = ExtractElements(selectOptionsStr); - auto& selectOptions = result.first; - auto& delimiter = result.second; - bool canBeNum = delimiter == "-"; - - if (delimiter.empty()) // doesn't have - { - description.push_back(line.substr(2)); - continue; - } + if (!strncmp(line.c_str(), "# ", 2)) + { + description.push_back(line.substr(2)); + continue; + } - if (canBeNum) - { - description.push_back(line.substr(2)); - } - else - { - description.push_back(line.substr(2, selectStartIdx - 3) + "."); - } + if (strncmp(line.c_str(), "# ", 2)) + { + // if option, e.g. #ConnectionTest=Send + size_t eqPos = line.find("="); + // if command, e.g. #ConnectionTest@Send Test E-Mail + size_t atPos = line.find("@"); - selectOpts = GetSelectOptions(selectOptions, canBeNum); + if (atPos != std::string::npos && eqPos == std::string::npos) + { + ManifestFile::Command command{}; + command.action = line.substr(atPos + 1); + Util::Trim(command.action); + ParseSectionAndSet(command, currSectionName, line, atPos); + command.description = std::move(description); + commands.push_back(std::move(command)); + description.clear(); + selectOpts.clear(); continue; } - if (!strncmp(line.c_str(), "# ", 2)) + if (eqPos != std::string::npos) { - description.push_back(line.substr(2)); + ManifestFile::Option option{}; + ParseSectionAndSet(option, currSectionName, line, eqPos); + bool canBeNum = !selectOpts.empty() && boost::variant2::get_if(&selectOpts[0]); + std::string value = line.substr(eqPos + 1); + Util::Trim(value); + option.value = GetSelectOpt(value, canBeNum); + option.description = std::move(description); + option.select = std::move(selectOpts); + options.push_back(std::move(option)); + description.clear(); + selectOpts.clear(); continue; } + } + } + } - if (strncmp(line.c_str(), "# ", 2)) - { - // if option, e.g. #ConnectionTest=Send - size_t eqPos = line.find("="); - // if command, e.g. #ConnectionTest@Send Test E-Mail - size_t atPos = line.find("@"); - - if (atPos != std::string::npos && eqPos == std::string::npos) - { - ManifestFile::Command command; - std::string name = line.substr(1, atPos - 1); - std::string action = line.substr(atPos + 1); - Util::Trim(action); - Util::Trim(name); - command.action = std::move(action); - command.name = std::move(name); - command.description = std::move(description); - command.displayName = command.name; - commands.push_back(std::move(command)); - description.clear(); - selectOpts.clear(); - continue; - } + std::vector + GetSelectOptions(const std::vector& opts, bool canBeNum) + { + std::vector selectOpts; - if (eqPos != std::string::npos) - { - ManifestFile::Option option; - std::string name = line.substr(1, eqPos - 1); - std::string value = line.substr(eqPos + 1); - Util::Trim(value); - Util::Trim(name); - bool canBeNum = !selectOpts.empty() && boost::variant2::get_if(&selectOpts[0]); - option.value = std::move(GetSelectOpt(value, canBeNum)); - option.name = std::move(name); - option.description = std::move(description); - option.select = std::move(selectOpts); - option.displayName = option.name; - options.push_back(std::move(option)); - description.clear(); - selectOpts.clear(); - continue; - } - } - } + for (const auto& option : opts) + { + selectOpts.push_back(GetSelectOpt(option, canBeNum)); } + return selectOpts; + } - std::vector - GetSelectOptions(const std::vector& opts, bool canBeNum) + ManifestFile::SelectOption GetSelectOpt(const std::string& val, bool canBeNum) + { + if (!canBeNum) { - std::vector selectOpts; + return ManifestFile::SelectOption(val); + } - for (const auto& option : opts) - { - selectOpts.push_back(GetSelectOpt(option, canBeNum)); - } - return selectOpts; + auto result = Util::StrToNum(val); + if (result.has_value()) + { + return ManifestFile::SelectOption(result.get()); } - ManifestFile::SelectOption GetSelectOpt(const std::string& val, bool canBeNum) + return ManifestFile::SelectOption(val); + } + + std::pair, std::string> + ExtractElements(const std::string& str) + { + std::vector elements; + std::string word; + + for (size_t i = 0; i < str.size(); ++i) { - if (!canBeNum) + if (i == (str.size() - 1)) { - return ManifestFile::SelectOption(val); + word += str[i]; + elements.push_back(std::move(word)); + break; } - auto result = Util::StrToNum(val); - if (result.has_value()) + if (str[i] == ',') { - return ManifestFile::SelectOption(result.get()); + elements.push_back(std::move(word)); + word.clear(); + continue; } - return ManifestFile::SelectOption(val); - } - - std::pair, std::string> - ExtractElements(const std::string& str) - { - std::vector elements; - std::string word; - - for (size_t i = 0; i < str.size(); ++i) + if (str[i] == ' ' && word.empty()) { - if (i == str.size() - 1) - { - word += str[i]; - elements.push_back(std::move(word)); - break; - } - - if (str[i] == ',') - { - elements.push_back(std::move(word)); - word.clear(); - continue; - } + continue; + } - if (str[i] == ' ' && word.empty()) - { - continue; - } + if (str[i] == ' ' && !word.empty()) + { + elements.clear(); + elements.push_back(str); + return std::make_pair(std::move(elements), ""); + } - if (str[i] == ' ' && !word.empty()) + if (str[i] == '-' && elements.empty()) + { + std::string restWord; + for (size_t j = i + 1; j < str.size(); ++j) { - elements.clear(); - elements.push_back(str); - return std::make_pair(std::move(elements), ""); - } + if (j >= str.size() - 1) + { + restWord += str[j]; + elements.push_back(std::move(word)); + elements.push_back(std::move(restWord)); + return std::make_pair(std::move(elements), "-"); + } - if (str[i] == '-' && elements.empty()) - { - std::string restWord; - for (size_t j = i + 1; j < str.size(); ++j) + if (str[j] == ' ') { - if (j >= str.size() - 1) - { - restWord += str[j]; - elements.push_back(std::move(word)); - elements.push_back(std::move(restWord)); - return std::make_pair(std::move(elements), "-"); - } - - if (str[j] == ' ') - { - elements.push_back(str); - return std::make_pair(std::move(elements), ""); - } - - if (str[j] == ',') - { - word += '-' + restWord; - elements.push_back(std::move(word)); - word.clear(); - i = j; - break; - } + elements.push_back(str); + return std::make_pair(std::move(elements), ""); + } - restWord += str[j]; + if (str[j] == ',') + { + word += '-' + restWord; + elements.push_back(std::move(word)); + word.clear(); + i = j; + break; } - continue; + restWord += str[j]; } - word += str[i]; + continue; } - return std::make_pair(std::move(elements), ","); + word += str[i]; } + + if (elements.size() == 1) + { + return std::make_pair(std::move(elements), ""); + } + + return std::make_pair(std::move(elements), ","); } } bool V2::Load(Extension::Script& script, const char* location, const char* rootDir) { - ManifestFile::Manifest manifest; + ManifestFile::Manifest manifest{}; if (!ManifestFile::Load(manifest, location)) return false; @@ -430,6 +438,7 @@ namespace ExtensionLoader script.SetHomepage(std::move(manifest.homepage)); script.SetLicense(std::move(manifest.license)); script.SetVersion(std::move(manifest.version)); + script.SetNzbgetMinVersion(std::move(manifest.nzbgetMinVersion)); script.SetDisplayName(std::move(manifest.displayName)); script.SetName(std::move(manifest.name)); script.SetAbout(std::move(manifest.about)); @@ -443,17 +452,14 @@ namespace ExtensionLoader return true; } - namespace + Extension::Kind GetScriptKind(const std::string& line) { - Extension::Kind GetScriptKind(const std::string& line) - { - Extension::Kind kind; - kind.post = strstr(line.c_str(), POST_SCRIPT_SIGNATURE) != nullptr; - kind.scan = strstr(line.c_str(), SCAN_SCRIPT_SIGNATURE) != nullptr; - kind.queue = strstr(line.c_str(), QUEUE_SCRIPT_SIGNATURE) != nullptr; - kind.scheduler = strstr(line.c_str(), SCHEDULER_SCRIPT_SIGNATURE) != nullptr; - kind.feed = strstr(line.c_str(), FEED_SCRIPT_SIGNATURE) != nullptr; - return kind; - } + Extension::Kind kind; + kind.post = strstr(line.c_str(), POST_SCRIPT_SIGNATURE) != nullptr; + kind.scan = strstr(line.c_str(), SCAN_SCRIPT_SIGNATURE) != nullptr; + kind.queue = strstr(line.c_str(), QUEUE_SCRIPT_SIGNATURE) != nullptr; + kind.scheduler = strstr(line.c_str(), SCHEDULER_SCRIPT_SIGNATURE) != nullptr; + kind.feed = strstr(line.c_str(), FEED_SCRIPT_SIGNATURE) != nullptr; + return kind; } } diff --git a/daemon/extension/ExtensionLoader.h b/daemon/extension/ExtensionLoader.h index 1fb504c5e..07c5612d7 100644 --- a/daemon/extension/ExtensionLoader.h +++ b/daemon/extension/ExtensionLoader.h @@ -26,6 +26,7 @@ namespace ExtensionLoader { + extern const char* DEFAULT_SECTION_NAME; extern const char* BEGIN_SCRIPT_SIGNATURE; extern const char* BEGIN_SCRIPT_COMMANDS_AND_OTPIONS; extern const char* POST_SCRIPT_SIGNATURE; @@ -47,20 +48,39 @@ namespace ExtensionLoader namespace V1 { bool Load(Extension::Script& script, const char* location, const char* rootDir); - namespace + + void ParseOptionsAndCommands( + std::ifstream& file, + std::vector& options, + std::vector& commands + ); + std::vector + GetSelectOptions(const std::vector& opts, bool isDashDelim); + ManifestFile::SelectOption GetSelectOpt(const std::string& val, bool canBeNum); + void RemoveTailAndTrim(std::string& str, const char* tail); + void BuildDisplayName(Extension::Script& script); + std::pair, std::string> + ExtractElements(const std::string& str); + template + void ParseSectionAndSet(T& opt, std::string sectionName, const std::string& line, size_t sepPos) { - void ParseOptionsAndCommands( - std::ifstream& file, - std::vector& options, - std::vector& commands - ); - std::vector - GetSelectOptions(const std::vector& opts, bool isDashDelim); - ManifestFile::SelectOption GetSelectOpt(const std::string& val, bool canBeNum); - void RemoveTailAndTrim(std::string& str, const char* tail); - void BuildDisplayName(Extension::Script& script); - std::pair, std::string> - ExtractElements(const std::string& str); + opt.name = line.substr(1, sepPos - 1); + Util::Trim(opt.name); + + ManifestFile::Section section{}; + section.name = std::move(sectionName); + + size_t digitPos = opt.name.find("1."); + if (digitPos != std::string::npos) + { + section.prefix = opt.name.substr(0, digitPos); + section.multi = true; + opt.name = opt.name.substr(digitPos + 2); + + } + + opt.section = std::move(section); + opt.displayName = opt.name; } } @@ -69,10 +89,7 @@ namespace ExtensionLoader bool Load(Extension::Script& script, const char* location, const char* rootDir); } - namespace - { - Extension::Kind GetScriptKind(const std::string& line); - } + Extension::Kind GetScriptKind(const std::string& line); } #endif diff --git a/daemon/extension/ExtensionManager.cpp b/daemon/extension/ExtensionManager.cpp index 1fe0cad54..550ff4def 100644 --- a/daemon/extension/ExtensionManager.cpp +++ b/daemon/extension/ExtensionManager.cpp @@ -29,7 +29,7 @@ namespace ExtensionManager { - const Extensions& Manager::GetExtensions() const + const Extensions& Manager::GetExtensions() const & { std::shared_lock lock{m_mutex}; return m_extensions; @@ -38,20 +38,21 @@ namespace ExtensionManager std::pair Manager::DownloadExtension(const std::string& url, const std::string& extName) { - BString<1024> tmpFileName; - tmpFileName.Format("%s%cextension-%s.tmp.zip", g_Options->GetTempDir(), PATH_SEPARATOR, extName.c_str()); + std::string tmpFileName = std::string(g_Options->GetTempDir()) + + PATH_SEPARATOR + + "extension-" + extName + ".tmp.zip"; std::unique_ptr downloader = std::make_unique(); downloader->SetUrl(url.c_str()); downloader->SetForce(true); downloader->SetRetry(false); - downloader->SetOutputFilename(tmpFileName); + downloader->SetOutputFilename(tmpFileName.c_str()); downloader->SetInfoName(extName.c_str()); WebDownloader::EStatus status = downloader->DownloadWithRedirects(5); downloader.reset(); - return std::make_pair(status, tmpFileName.Str()); + return std::make_pair(status, std::move(tmpFileName)); } boost::optional @@ -111,16 +112,16 @@ namespace ExtensionManager }; unpacker.SetArgs(std::move(args)); - int res = unpacker.Execute(); + int ec = unpacker.Execute(); - if (res != 0) + if (ec < 0) { - if (!FileSystem::DeleteFile(filename.c_str())) - { - return "Failed to unpack and delete temp file: " + filename; - } + return "Failed to unpack " + filename + ". Make sure that the path to 7-Zip is valid."; + } - return "Failed to unpack " + filename; + if (ec > 0) + { + return "Failed to unpack " + filename + ". " + UnpackController::DecodeSevenZipExitCode(ec); } if (!FileSystem::DeleteFile(filename.c_str())) diff --git a/daemon/extension/ExtensionManager.h b/daemon/extension/ExtensionManager.h index 6a9c63f9a..f0c1c61d3 100644 --- a/daemon/extension/ExtensionManager.h +++ b/daemon/extension/ExtensionManager.h @@ -1,7 +1,7 @@ /* * This file is part of nzbget. See . * - * Copyright (C) 2023 Denis + * Copyright (C) 2023-2024 Denis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,7 +33,7 @@ namespace ExtensionManager { using Extensions = std::vector>; - class Manager + class Manager final { public: Manager() = default; @@ -60,7 +60,7 @@ namespace ExtensionManager std::pair DownloadExtension(const std::string& url, const std::string& info); - const Extensions& GetExtensions() const; + const Extensions& GetExtensions() const &; private: void LoadExtensionDir(const char* directory, bool isSubDir, const char* rootDir); diff --git a/daemon/extension/ManifestFile.cpp b/daemon/extension/ManifestFile.cpp index bed19e5ef..8a01e5bf1 100644 --- a/daemon/extension/ManifestFile.cpp +++ b/daemon/extension/ManifestFile.cpp @@ -1,7 +1,7 @@ /* * This file is part of nzbget. See . * - * Copyright (C) 2023 Denis + * Copyright (C) 2023-2024 Denis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,6 +27,7 @@ namespace ManifestFile { const char* MANIFEST_FILE = "manifest.json"; + const char* DEFAULT_SECTION_NAME = "options"; bool Load(Manifest& manifest, const char* directory) { @@ -45,188 +46,263 @@ namespace ManifestFile if (!ValidateAndSet(json, manifest)) return false; + CheckKeyAndSet(json, "nzbgetMinVersion", manifest.nzbgetMinVersion); + return true; } - namespace + bool ValidateAndSet(const Json::JsonObject& json, Manifest& manifest) { - bool ValidateAndSet(const Json::JsonObject& json, Manifest& manifest) - { - if (!CheckKeyAndSet(json, "author", manifest.author)) - return false; + if (!CheckKeyAndSet(json, "author", manifest.author)) + return false; - if (!CheckKeyAndSet(json, "main", manifest.main)) - return false; + if (!CheckKeyAndSet(json, "main", manifest.main)) + return false; - if (!CheckKeyAndSet(json, "homepage", manifest.homepage)) - return false; + if (!CheckKeyAndSet(json, "homepage", manifest.homepage)) + return false; - if (!CheckKeyAndSet(json, "about", manifest.about)) - return false; + if (!CheckKeyAndSet(json, "about", manifest.about)) + return false; - if (!CheckKeyAndSet(json, "version", manifest.version)) - return false; + if (!CheckKeyAndSet(json, "version", manifest.version)) + return false; - if (!CheckKeyAndSet(json, "name", manifest.name)) - return false; + if (!CheckKeyAndSet(json, "name", manifest.name)) + return false; - if (!CheckKeyAndSet(json, "displayName", manifest.displayName)) - return false; + if (!CheckKeyAndSet(json, "displayName", manifest.displayName)) + return false; - if (!CheckKeyAndSet(json, "kind", manifest.kind)) - return false; + if (!CheckKeyAndSet(json, "kind", manifest.kind)) + return false; - if (!CheckKeyAndSet(json, "license", manifest.license)) - return false; + if (!CheckKeyAndSet(json, "license", manifest.license)) + return false; - if (!CheckKeyAndSet(json, "taskTime", manifest.taskTime)) - return false; + if (!CheckKeyAndSet(json, "taskTime", manifest.taskTime)) + return false; - if (!CheckKeyAndSet(json, "queueEvents", manifest.queueEvents)) - return false; + if (!CheckKeyAndSet(json, "queueEvents", manifest.queueEvents)) + return false; - if (!ValidateOptionsAndSet(json, manifest.options)) - return false; + if (!ValidateOptionsAndSet(json, manifest.options)) + return false; - if (!ValidateCommandsAndSet(json, manifest.commands)) - return false; + if (!ValidateCommandsAndSet(json, manifest.commands)) + return false; - if (!ValidateTxtAndSet(json, manifest.description, "description")) - return false; + ValidateSectionsAndSet(json, manifest.options, manifest.commands); - if (!ValidateTxtAndSet(json, manifest.requirements, "requirements")) - return false; + if (!ValidateTxtAndSet(json, manifest.description, "description")) + return false; - return true; - } + if (!ValidateTxtAndSet(json, manifest.requirements, "requirements")) + return false; - bool ValidateCommandsAndSet(const Json::JsonObject& json, std::vector& commands) - { - auto rawCommands = json.if_contains("commands"); - if (!rawCommands || !rawCommands->is_array()) - return false; + return true; + } - for (auto& value : rawCommands->as_array()) - { - Json::JsonObject cmdJson = value.as_object(); - Command command; + bool ValidateCommandsAndSet(const Json::JsonObject& json, std::vector& commands) + { + auto rawCommands = json.if_contains("commands"); + if (!rawCommands || !rawCommands->is_array()) + return false; - if (!CheckKeyAndSet(cmdJson, "name", command.name)) - continue; + for (auto& value : rawCommands->as_array()) + { + Json::JsonObject cmdJson = value.as_object(); + Command command{}; - if (!CheckKeyAndSet(cmdJson, "displayName", command.displayName)) - continue; + CheckKeyAndSet(cmdJson, "section", command.section.name, DEFAULT_SECTION_NAME); - if (!ValidateTxtAndSet(cmdJson, command.description, "description")) - continue; + if (!CheckKeyAndSet(cmdJson, "name", command.name)) + continue; - if (!CheckKeyAndSet(cmdJson, "action", command.action)) - continue; + if (!CheckKeyAndSet(cmdJson, "displayName", command.displayName)) + continue; - commands.emplace_back(std::move(command)); - } + if (!ValidateTxtAndSet(cmdJson, command.description, "description")) + continue; - commands.shrink_to_fit(); + if (!CheckKeyAndSet(cmdJson, "action", command.action)) + continue; - return true; + commands.emplace_back(std::move(command)); } - bool ValidateOptionsAndSet(const Json::JsonObject& json, std::vector