diff --git a/.github/actions/install_lazarus/action.yaml b/.github/actions/install_lazarus/action.yaml new file mode 100644 index 0000000..f62e0c5 --- /dev/null +++ b/.github/actions/install_lazarus/action.yaml @@ -0,0 +1,101 @@ +# include this file in the cicd_windows.yaml, cicd_ubuntu.yaml, and cicd_macos.yaml like this: +# - name: Install Lazarus IDE +# uses: ./.github/actions/install_lazarus +# with: +# qt_version_ci: 6 # optional, only needed when building with qt5 or qt6 + + +# https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action +name: 'Install Lazaurus IDE' +description: 'download and build lazbuild' + +inputs: + qt_version_ci: # id of input + description: 'QT version' + required: false + +runs: + using: "composite" + steps: + - name: Install Lazarus IDE (Windows) + if: runner.os == 'Windows' + run: | + winget install lazarus --disable-interactivity --accept-source-agreements --silent + echo 'c:/lazarus' >> $GITHUB_PATH + shell: pwsh + + - name: Make PATH also in bash available (Windows) + if: runner.os == 'Windows' + run: | + echo 'c:/lazarus' >> $GITHUB_PATH + shell: bash + + - name: Install fpc (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y fpc + shell: bash + + - name: Install dependency for gtk2 + if: runner.os == 'Linux' && inputs.qt_version_ci == '' + run: sudo apt-get install -y libgtk2.0-dev + shell: bash + + - name: Install dependency for qt5 + if: runner.os == 'Linux' && inputs.qt_version_ci == '5' + run: sudo apt-get install -y libqt5x11extras5-dev + shell: bash + + - name: Install dependency for qt6 + if: runner.os == 'Linux' && inputs.qt_version_ci == '6' + run: sudo apt-get install -y qt6-base-dev + shell: bash + + - name: Install Free Pascal Compiler (FPC) multi arch version for macOS x86_64 and aarch64 (macOS) + if: runner.os == 'macOS' + run: brew install fpc + shell: bash + + - name: Download Lazarus source code into temp folder (Linux and macOS) + if: runner.os != 'Windows' + env: + # Linux and macOS need to build lazbuild from Lazarus source code. + # Copied the latest Lazarus source code from: https://sourceforge.net/projects/lazarus/files/Lazarus%20Zip%20_%20GZip/ + LAZARUS_URL_TAR_GZ: "https://github.com/GerryFerdinandus/bittorrent-tracker-editor/releases/download/V1.32.0/lazarus.tar.gz" + RUNNER_TEMP: ${{ runner.temp }} + run: | + #Download lazarus source code. Directory 'lazarus' will be created in the temp folder. + echo ${RUNNER_TEMP} + cd ${RUNNER_TEMP} + curl -L -O ${{ env.LAZARUS_URL_TAR_GZ }} + tar -xzf *.tar.gz + shell: bash + + - name: Build lazbuild from Lazarus source code (Linux and macOS) + if: runner.os != 'Windows' + env: + RUNNER_TEMP: ${{ runner.temp }} + run: | + # make lazbuild and put the link with extra parameter in the temp folder. + LAZARUS_DIR=${RUNNER_TEMP}/lazarus + cd "$LAZARUS_DIR" + make lazbuild + echo "$LAZARUS_DIR/lazbuild --primary-config-path=$LAZARUS_DIR --lazarusdir=$LAZARUS_DIR \$*" > ${RUNNER_TEMP}/lazbuild + chmod +x ${RUNNER_TEMP}/lazbuild + # Add lazbuild to the PATH variable. + echo ${RUNNER_TEMP} >> $GITHUB_PATH + shell: bash + + - name: Build libQTpas.so (qt5 or qt6) + if: runner.os == 'Linux' && inputs.qt_version_ci != '' + env: + RUNNER_TEMP: ${{ runner.temp }} + run: | + cd "${RUNNER_TEMP}/lazarus/lcl/interfaces/qt${{ inputs.qt_version_ci }}/cbindings/" + /usr/lib/qt${{ inputs.qt_version_ci }}/bin/qmake + make -j$(nproc) + sudo make install + shell: bash + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5b52009 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# Enable version updates for GitHub Actions at cron schedule every 6 months +# Can be manually triggered via menu: Insights > Dependency graph > "Recent update jobs" -> "Check for updates" + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "cron" + cronjob: "0 0 1 1/6 *" + commit-message: + prefix: "chore" + include: "scope" + open-pull-requests-limit: 5 diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml deleted file mode 100644 index cc2d0a2..0000000 --- a/.github/workflows/cicd.yml +++ /dev/null @@ -1,207 +0,0 @@ -name: CI/CD with Lazarus IDE on multiple operating systems. - -permissions: - contents: write - -on: - push: - pull_request: - workflow_dispatch: - # Automatic cron build every 6 months to check if everything still works. - schedule: - - cron: "0 0 1 1/6 *" - -jobs: - build: - runs-on: ${{ matrix.os }} - - strategy: - # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. - fail-fast: false - - # Set up an array to perform the following three build configurations. - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - include: - - os: windows-latest - LAZBUILD_WITH_PATH: c:/lazarus/lazbuild - RELEASE_ZIP_FILE: trackereditor_windows_amd64.zip - LAZ_OPT: - - os: ubuntu-latest - LAZBUILD_WITH_PATH: lazbuild - RELEASE_ZIP_FILE: trackereditor_linux_amd64.zip - LAZ_OPT: - - os: macos-latest - LAZBUILD_WITH_PATH: /Applications/Lazarus/lazbuild - RELEASE_ZIP_FILE: trackereditor_macOS_amd64.zip - LAZ_OPT: --widgetset=cocoa - - steps: - - uses: actions/checkout@v4 - - - name: Install Lazarus IDE - run: | - if [ "$RUNNER_OS" == "Linux" ]; then - sudo apt install -y lazarus zip xvfb - elif [ "$RUNNER_OS" == "Windows" ]; then - choco install lazarus zip - # https://wiki.overbyte.eu/wiki/index.php/ICS_Download#Download_OpenSSL_Binaries - curl -L -O --output-dir enduser https://github.com/GerryFerdinandus/bittorrent-tracker-editor/releases/download/V1.32.0/libssl-3-x64.dll - curl -L -O --output-dir enduser https://github.com/GerryFerdinandus/bittorrent-tracker-editor/releases/download/V1.32.0/libcrypto-3-x64.dll - elif [ "$RUNNER_OS" == "macOS" ]; then - brew install --cask lazarus - else - echo "$RUNNER_OS not supported" - exit 1 - fi - shell: bash - - - name: Build Release version - # Build trackereditor project (Release mode) - run: ${{ matrix.LAZBUILD_WITH_PATH }} --build-mode=Release ${{ matrix.LAZ_OPT }} source/project/tracker_editor/trackereditor.lpi - shell: bash - - - name: Build Unit Test on Windows - if: matrix.os == 'windows-latest' - # Build unit test project (Debug mode) - run: ${{ matrix.LAZBUILD_WITH_PATH }} --build-mode=Debug ${{ matrix.LAZ_OPT }} source/project/unit_test/tracker_editor_test.lpi - shell: bash - - - name: Run Unit Test on Windows - if: matrix.os == 'windows-latest' - # Also remove all the extra file created by test. - # We do not what it in the ZIP release files. - # Undo all changes made by testing. - run: | - set -e - enduser/test_trackereditor -a --format=plain - set +e - - # remove file created by unit test - rm -f enduser/console_log.txt - rm -f enduser/export_trackers.txt - git reset --hard - shell: bash - - - name: Test OpenSSL works on Linux CI - if: matrix.os == 'ubuntu-latest' - run: xvfb-run --auto-servernum enduser/trackereditor -TEST_SSL - - - name: Create a zip file for Linux release. - if: matrix.os == 'ubuntu-latest' - run: zip -j ${{ matrix.RELEASE_ZIP_FILE }} enduser/*.txt enduser/trackereditor - shell: bash - - - name: Create a zip file for Windows release. - if: matrix.os == 'windows-latest' - run: | - zip -j ${{ matrix.RELEASE_ZIP_FILE }} enduser/*.txt enduser/trackereditor.exe enduser/*.dll - shell: bash - - - name: Move file into macOS .app - if: matrix.os == 'macos-latest' - run: | - # copy everything into enduser/macos/app folder - # - # Move the executable to the application bundle - mv enduser/trackereditor enduser/macos/app/trackereditor.app/Contents/MacOS - - # Move the trackers list to application bundle - mv enduser/add_trackers.txt enduser/macos/app/trackereditor.app/Contents/MacOS - mv enduser/remove_trackers.txt enduser/macos/app/trackereditor.app/Contents/MacOS - - # move all the *.txt file - mv enduser/*.txt enduser/macos/app - - # zip only the app folder with extra text file. - # /usr/bin/ditto -c -k "enduser/macos/app" "${{ matrix.RELEASE_ZIP_FILE }}" - shell: bash - - - name: Codesign macOS app bundle - # This macOS Codesign step is copied from: - # https://federicoterzi.com/blog/automatic-code-signing-and-notarization-for-macos-apps-using-github-actions/ - # This is a bit different from the previous version for Travis-CI build system to build bittorrent tracker editor - if: matrix.os == 'macos-latest' - env: - MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }} - MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }} - MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }} - MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }} - run: | - # Turn our base64-encoded certificate back to a regular .p12 file - echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12 - - # We need to create a new keychain, otherwise using the certificate will prompt - # with a UI dialog asking for the certificate password, which we can't - # use in a headless CI environment - - security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain - security default-keychain -s build.keychain - security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain - security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain - - # We finally codesign our app bundle, specifying the Hardened runtime option. - #/usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime enduser/macos/app/trackereditor.app -v - - # sign the app. -sign is the developer cetificate ID - # entitlements does not work at this moment - #codesign --timestamp --entitlements enduser/macos/entitlements.plist --force --options runtime --deep --sign $CERTIFICATE_ID $FILE_APP - - # Please note: this is the same code version used in Travis-CI - /usr/bin/codesign --timestamp --force --options runtime --deep --sign "$MACOS_CERTIFICATE_NAME" enduser/macos/app/trackereditor.app - shell: bash - - - name: Notarize macOS app bundle - if: matrix.os == 'macos-latest' - env: - PROD_MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }} - PROD_MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }} - PROD_MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }} - run: | - # Store the notarization credentials so that we can prevent a UI password dialog - # from blocking the CI - - echo "Create keychain profile" - xcrun notarytool store-credentials "notarytool-profile" --apple-id "$PROD_MACOS_NOTARIZATION_APPLE_ID" --team-id "$PROD_MACOS_NOTARIZATION_TEAM_ID" --password "$PROD_MACOS_NOTARIZATION_PWD" - - # We can't notarize an app bundle directly, but we need to compress it as an archive. - # Therefore, we create a zip file containing our app bundle, so that we can send it to the - # notarization service - - echo "Creating temp notarization archive" - ditto -c -k --keepParent "enduser/macos/app/trackereditor.app" "notarization.zip" - - # Here we send the notarization request to the Apple's Notarization service, waiting for the result. - # This typically takes a few seconds inside a CI environment, but it might take more depending on the App - # characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if - # you're curious - - echo "Notarize app" - xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait - - # Finally, we need to "attach the staple" to our executable, which will allow our app to be - # validated by macOS even when an internet connection is not available. - echo "Attach staple" - xcrun stapler staple "enduser/macos/app/trackereditor.app" - - # Remove notarization.zip, otherwise it will also be 'released' to the end user - rm -f "notarization.zip" - - # zip only the app folder with extra text file. - echo "Zip macOS app file" - /usr/bin/ditto -c -k "enduser/macos/app" "${{ matrix.RELEASE_ZIP_FILE }}" - shell: bash - - - name: Upload Artifact - uses: actions/upload-artifact@v3 - with: - path: ${{ matrix.RELEASE_ZIP_FILE }} - if-no-files-found: error - - - name: Zip file release to end user - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') - with: - files: | - *.zip diff --git a/.github/workflows/cicd_macos.yaml b/.github/workflows/cicd_macos.yaml new file mode 100644 index 0000000..92b4ed2 --- /dev/null +++ b/.github/workflows/cicd_macos.yaml @@ -0,0 +1,311 @@ +# CI/CD workflow for macOS systems to build bittorrent tracker editor app +# Uses GitHub Actions macOS runners to build the app for both Apple silicon (aarch64) and Intel Mac (x86_64) +# Then create a Universal binary using lipo tool. +# Finally create a dmg file for end user distribution. +# The build also codesign and notarize the dmg file if the required Apple developer certificate +# and notarization credentials are present in the GitHub secrets. +# The build is triggered on push, pull request, manual workflow dispatch and every 6 months cron job. +# +# macos-15-intel runner is used to build the x86_64 version. +# macos-latest runner is used to build the aarch64 version and also to create the Universal binary dmg file. +# The build uses Free Pascal Compiler (FPC) and Lazarus IDE to build the app. +# +# macos-15-intel runner is the last macOS runner that supports Intel architecture. +# Newer macOS runners only support Apple silicon (aarch64) architecture. +# Keep supporting Intel architecture build while there runners are still available. +# +# Must use macOS ditto tool to create zip files. +# Using zip command creates zip files that missing some metadata required for macOS apps. + + +name: CI/CD on macOS systems. + +permissions: + contents: write + +on: + push: + pull_request: + workflow_dispatch: + # Automatic cron build every 6 months to check if everything still works. + schedule: + - cron: "0 0 1 1/6 *" + +env: + MACOS_APP: enduser/trackereditor.app + PROGRAM_NAME_WITH_PATH: 'enduser/trackereditor' + BUILD_WITH_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }} + PROJECT_LPI: source/project/tracker_editor/trackereditor.lpi + RELEASE_DMG_FILE: trackereditor_macOS_notarized_universal_binary.dmg + +jobs: + build: + timeout-minutes: 60 + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. + fail-fast: false + + # Set up an include to perform the following build configurations. + matrix: + include: + - BUILD_TARGET: build_aarch64 + ARCH: aarch64 + RUNS_ON: macos-latest + + - BUILD_TARGET: build_x86_64 + ARCH: x86_64 + RUNS_ON: macos-15-intel + + name: ${{ matrix.BUILD_TARGET }} + runs-on: ${{ matrix.RUNS_ON }} + + steps: + - uses: actions/checkout@v6 + with: + submodules: true + + - name: Install Lazarus IDE + uses: ./.github/actions/install_lazarus + + - name: Build trackereditor app for Apple (${{ matrix.ARCH }}) + run: | + lazbuild --build-all --build-mode=Release --widgetset=cocoa --cpu=${{ matrix.ARCH }} ${{ env.PROJECT_LPI }} + shell: bash + + - name: Test App SSL connection + run: open "${{ env.PROGRAM_NAME_WITH_PATH }}" --args -TEST_SSL + shell: bash + + - name: Zip the macOS .app bundle for artifact upload + if: matrix.ARCH == 'aarch64' + run: ditto -c -k enduser/trackereditor.app enduser/trackereditor_app.zip + shell: bash + + - name: Rename built -aarch64 binary for artifact upload + run: | + mv ${{ env.PROGRAM_NAME_WITH_PATH }} ${{ env.PROGRAM_NAME_WITH_PATH }}-${{ matrix.ARCH }} + ditto -c -k ${{ env.PROGRAM_NAME_WITH_PATH }}-${{ matrix.ARCH }} ${{ env.PROGRAM_NAME_WITH_PATH }}-${{ matrix.ARCH }}.zip + shell: bash + + - name: Upload Artifact. + uses: actions/upload-artifact@v6 + with: + name: artifact-${{ matrix.ARCH }} + # Include both the zipped universal app bundle and the aarch64 binary zip + path: enduser/*.zip + if-no-files-found: error + + create_universal_macOS_binary: # Create Universal binary from aarch64 and x86_64 builds + needs: build + runs-on: macos-latest + timeout-minutes: 60 + + steps: + - uses: actions/checkout@v6 + with: + sparse-checkout: metainfo + + - name: Install create-dmg tool + run: brew install create-dmg + shell: bash + + - name: Download build artifacts from previous jobs + uses: actions/download-artifact@v7 + with: + path: enduser/ + merge-multiple: true + + - name: Display the downloaded artifact files + run: ls -R enduser/ + shell: bash + + - name: Unzip all the artifact files + run: | + ditto -xk enduser/trackereditor_app.zip enduser/trackereditor.app + ditto -xk ${{ env.PROGRAM_NAME_WITH_PATH }}-x86_64.zip enduser/ + ditto -xk ${{ env.PROGRAM_NAME_WITH_PATH }}-aarch64.zip enduser/ + # Remove the zip files after extraction + rm -f enduser/*.zip + shell: bash + + - name: Display the downloaded artifact files after unzip + run: ls -R enduser/ + shell: bash + + - name: Set correct version number in macOS .app bundle Info.plist from metainfo xml file + env: + METAINFO_FILE: metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.metainfo.xml + run: | + TRACKER_EDITOR_VERSION=$(xmllint --xpath "string(/component/releases/release[1]/@version)" $METAINFO_FILE) + echo Program version: $TRACKER_EDITOR_VERSION + plutil -replace CFBundleShortVersionString -string $TRACKER_EDITOR_VERSION ${{ env.MACOS_APP }}/Contents/Info.plist + shell: bash + + - name: Create and set app icon in macOS .app bundle + env: + ICON_FILE: 'metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.png' + run: | + + # ------ Create icon set and move it into the app + iconset_folder="temp_folder.iconset" + rm -rf "${iconset_folder}" + mkdir -p "${iconset_folder}" + + for s in 16 32 128 256 512; do + d=$(($s*2)) + sips -Z $s $ICON_FILE --out "${iconset_folder}/icon_${s}x$s.png" + sips -Z $d $ICON_FILE --out "${iconset_folder}/icon_${s}x$s@2x.png" + done + + # create .icns icon file + iconutil -c icns "${iconset_folder}" -o "iconfile.icns" + rm -r "${iconset_folder}" + + # move icon file to the app + mv -f "iconfile.icns" "${{ env.MACOS_APP }}/Contents/Resources" + + # add icon to plist xml file CFBundleIconFile = "iconfile" + plutil -insert CFBundleIconFile -string "iconfile" "${{ env.MACOS_APP }}/Contents/Info.plist" + shell: bash + + - name: Create a Universal macOS binary from aarch64 and x86_64 + run: | + # Create Universal binary using lipo tool + lipo -create -output ${{ env.PROGRAM_NAME_WITH_PATH }} ${{ env.PROGRAM_NAME_WITH_PATH }}-aarch64 ${{ env.PROGRAM_NAME_WITH_PATH }}-x86_64 + + # Remove the previous architecture specific binaries + rm -f ${{ env.PROGRAM_NAME_WITH_PATH }}-* + shell: bash + + - name: Replace the macOS .app bundle binary with the Universal binary + run: | + PROGRAM_NAME_ONLY=$(basename -- "${{ env.PROGRAM_NAME_WITH_PATH }}") + # remove the previous app (symbolic link) + rm -f "${{ env.MACOS_APP }}/Contents/MacOS/${PROGRAM_NAME_ONLY}" + # copy the program to the app version. + mv -f "${{ env.PROGRAM_NAME_WITH_PATH }}" "${{ env.MACOS_APP }}/Contents/MacOS" + ls -l "${{ env.MACOS_APP }}/Contents/MacOS/" + shell: bash + + - name: Display the enduser/ folder contents + run: ls -R enduser/ + shell: bash + + - name: Verify the Universal binary architectures + run: | + lipo -archs "${{ env.MACOS_APP }}"/Contents/MacOS/trackereditor + lipo -archs "${{ env.MACOS_APP }}"/Contents/MacOS/trackereditor | grep -Fq x86_64 + lipo -archs "${{ env.MACOS_APP }}"/Contents/MacOS/trackereditor | grep -Fq arm64 + shell: bash + + - name: Test App SSL connection + run: open "${{ env.MACOS_APP }}" --args -TEST_SSL + shell: bash + + - name: Codesign macOS app bundle. If certificate is present. + if: ${{ env.BUILD_WITH_CERTIFICATE != '' }} + # This macOS Codesign step is copied from: + # https://federicoterzi.com/blog/automatic-code-signing-and-notarization-for-macos-apps-using-github-actions/ + # This is a bit different from the previous version for Travis-CI build system to build bittorrent tracker editor + # More info https://developer.apple.com/forums/thread/128166 + env: + MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }} + MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }} + MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }} + MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }} + run: | + # Turn our base64-encoded certificate back to a regular .p12 file + echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12 + + # We need to create a new keychain, otherwise using the certificate will prompt + # with a UI dialog asking for the certificate password, which we can't + # use in a headless CI environment + + security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain + security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain + + # sign the app. -sign is the developer cetificate ID + /usr/bin/codesign --timestamp --force --options runtime --sign "$MACOS_CERTIFICATE_NAME" "${{ env.MACOS_APP }}" + shell: bash + + - name: Display the enduser/ folder contents + run: ls -R enduser/ + shell: bash + + - name: Create dmg file from the enduser/ folder + run: | + # Build dmg image. https://github.com/create-dmg/create-dmg + create-dmg \ + --volname "bittorrent-tracker-editor" \ + --window-pos 200 120 \ + --window-size 800 400 \ + --icon "trackereditor.app" 200 190 \ + --app-drop-link 600 185 \ + ${{ env.RELEASE_DMG_FILE }} \ + "./enduser" + shell: bash + + - name: Codesign dmg file. If certificate is present. + if: ${{ env.BUILD_WITH_CERTIFICATE != '' }} + env: + MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }} + run: | + /usr/bin/codesign --timestamp --force --options runtime --sign "$MACOS_CERTIFICATE_NAME" "${{ env.RELEASE_DMG_FILE }}" + shell: bash + + - name: Notarize macOS DMG bundle. If certificate is present. + if: ${{ env.BUILD_WITH_CERTIFICATE != '' }} + env: + PROD_MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }} + PROD_MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }} + PROD_MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }} + run: | + # Store the notarization credentials so that we can prevent a UI password dialog + # from blocking the CI + + echo "Create keychain profile" + xcrun notarytool store-credentials "notarytool-profile" --apple-id "$PROD_MACOS_NOTARIZATION_APPLE_ID" --team-id "$PROD_MACOS_NOTARIZATION_TEAM_ID" --password "$PROD_MACOS_NOTARIZATION_PWD" + + # We can't notarize an app bundle directly, but we need to compress it as an archive. + # Therefore, we create a zip file containing our app bundle, so that we can send it to the + # notarization service + + echo "Creating temp notarization archive" + ditto -c -k --keepParent "${{ env.RELEASE_DMG_FILE }}" "notarization.zip" + + # Here we send the notarization request to the Apple's Notarization service, waiting for the result. + # This typically takes a few seconds inside a CI environment, but it might take more depending on the App + # characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if + # you're curious + + echo "Notarize app" + xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait + + # Finally, we need to "attach the staple" to our executable, which will allow our app to be + # validated by macOS even when an internet connection is not available. + echo "Attach staple" + xcrun stapler staple "${{ env.RELEASE_DMG_FILE }}" + shell: bash + + - name: Use diferent .dmg file name for non signed/notarize version + if: ${{ env.BUILD_WITH_CERTIFICATE == '' }} + run: mv ${{ env.RELEASE_DMG_FILE }} trackereditor_macOS_UNSIGNED_universal_binary.dmg + shell: bash + + - name: Upload Artifact. Signed/Notarize is optional. + uses: actions/upload-artifact@v6 + with: + name: artifact-${{ runner.os }} + path: "*.dmg" + compression-level: 0 # no compression. Content is already a zip file + if-no-files-found: error + + - name: Notarize file release to end user. If certificate is present. + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') && (env.BUILD_WITH_CERTIFICATE != '') + with: + files: ${{ env.RELEASE_DMG_FILE }} diff --git a/.github/workflows/cicd_ubuntu.yaml b/.github/workflows/cicd_ubuntu.yaml new file mode 100644 index 0000000..82de8e1 --- /dev/null +++ b/.github/workflows/cicd_ubuntu.yaml @@ -0,0 +1,153 @@ +name: CI/CD on Linux systems. + +permissions: + contents: write + +on: + push: + pull_request: + workflow_dispatch: + # Automatic cron build every 6 months to check if everything still works. + schedule: + - cron: "0 0 1 1/6 *" + +jobs: + build: + timeout-minutes: 60 + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. + fail-fast: false + + # Set up an include to perform the following build configurations. + matrix: + include: + - BUILD_TARGET: gtk2_amd64 + RELEASE_FILE_NAME: trackereditor_linux_amd64_gtk2.zip + LAZ_OPT: --widgetset=gtk2 + RUNS_ON: ubuntu-24.04 + + - BUILD_TARGET: qt5_amd64 + RELEASE_FILE_NAME: trackereditor_linux_amd64_qt5.zip + LAZ_OPT: --widgetset=qt5 + QT_VERSION_CI: '5' + RUNS_ON: ubuntu-24.04 + + - BUILD_TARGET: qt6_amd64 + RELEASE_FILE_NAME: trackereditor_linux_amd64_qt6.zip + LAZ_OPT: --widgetset=qt6 + QT_VERSION_CI: '6' + RUNS_ON: ubuntu-24.04 + + - BUILD_TARGET: AppImage_amd64 + RELEASE_FILE_NAME: trackereditor_linux_amd64_qt6.AppImage + LAZ_OPT: --widgetset=qt6 + QT_VERSION_CI: '6' + LINUX_DEPLOY_FILE_CPU: x86_64 + RUNS_ON: ubuntu-22.04 + + - BUILD_TARGET: AppImage_arm64 + RELEASE_FILE_NAME: trackereditor_linux_arm64_qt6.AppImage + LAZ_OPT: --widgetset=qt6 + QT_VERSION_CI: '6' + LINUX_DEPLOY_FILE_CPU: aarch64 + RUNS_ON: ubuntu-22.04-arm + + name: ${{ matrix.BUILD_TARGET }} + runs-on: ${{ matrix.RUNS_ON }} + + steps: + - uses: actions/checkout@v6 + with: + submodules: true + + - name: Install Lazarus IDE + uses: ./.github/actions/install_lazarus + with: + qt_version_ci: ${{ matrix.QT_VERSION_CI }} + + - name: Install dependency for all build + run: | + sudo apt-get update + sudo apt-get install -y xvfb + shell: bash + + - name: Install dependency for AppImage + if: matrix.BUILD_TARGET == 'AppImage_amd64' || matrix.BUILD_TARGET == 'AppImage_arm64' + run: | + # Add wayland plugin and platform theme + sudo apt-get install -y fuse qt6-wayland qt6-xdgdesktopportal-platformtheme qt6-gtk-platformtheme + # Download/Install AppImage tools + curl -L -O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-${{ matrix.LINUX_DEPLOY_FILE_CPU }}.AppImage + curl -L -O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-${{ matrix.LINUX_DEPLOY_FILE_CPU }}.AppImage + curl -L -O https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-${{ matrix.LINUX_DEPLOY_FILE_CPU }}.AppImage + chmod +x linuxdeploy-*.AppImage + shell: bash + + - name: Build trackereditor + # Build trackereditor project (Release mode) + run: lazbuild --build-all --build-mode=Release ${{ matrix.LAZ_OPT }} source/project/tracker_editor/trackereditor.lpi + shell: bash + + - name: Test if OpenSSL works on Linux CI + run: xvfb-run --auto-servernum enduser/trackereditor -TEST_SSL + shell: bash + + - name: Copy libQtpas.so before releasing the Qt5/Qt6 zip file format. + if: matrix.BUILD_TARGET == 'qt5_amd64' || matrix.BUILD_TARGET == 'qt6_amd64' + run: | + cp -av /usr/lib/*/libQt?Pas.* ${{ github.workspace }}/enduser + cat < ${{ github.workspace }}/enduser/missing_libQtPas.so.txt + Start program with: + env LD_LIBRARY_PATH=. ./trackereditor + + This uses libQtpas.so, which is necessary for this program. + Some Linux OS stores may also offer this libQtpas.so + https://archlinux.org/packages/extra/x86_64/qt5pas/ + https://archlinux.org/packages/extra/x86_64/qt6pas/ + EOF + shell: bash + + - name: Create a gtk2 or Qt5/Qt6 release in zip file format. + if: matrix.BUILD_TARGET != 'AppImage_amd64' + run: zip -j ${{ matrix.RELEASE_FILE_NAME }} enduser/*.txt enduser/libQt* enduser/trackereditor + shell: bash + + - name: Create AppImage + if: matrix.BUILD_TARGET == 'AppImage_amd64' || matrix.BUILD_TARGET == 'AppImage_arm64' + # LDAI_NO_APPSTREAM=1: skip checking AppStream metadata for issues + env: + LDAI_NO_APPSTREAM: 1 + LDAI_OUTPUT: ${{ matrix.RELEASE_FILE_NAME }} + QMAKE: /usr/lib/qt${{ matrix.QT_VERSION_CI }}/bin/qmake + EXTRA_QT_MODULES: waylandcompositor + EXTRA_PLATFORM_PLUGINS: libqwayland-generic.so;libqwayland-egl.so + DEPLOY_PLATFORM_THEMES: true + run: | + ./linuxdeploy-${{ matrix.LINUX_DEPLOY_FILE_CPU }}.AppImage \ + --output appimage \ + --appdir temp_appdir \ + --plugin qt \ + --executable enduser/trackereditor \ + --desktop-file metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.desktop \ + --icon-file metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.png + shell: bash + + - name: Test AppImage + if: matrix.BUILD_TARGET == 'AppImage_amd64' + run: xvfb-run --auto-servernum ./${{ matrix.RELEASE_FILE_NAME }} -TEST_SSL + shell: bash + + - name: Upload Artifact + uses: actions/upload-artifact@v6 + with: + name: artifact-${{ matrix.RELEASE_FILE_NAME }} + path: ${{ matrix.RELEASE_FILE_NAME }} + compression-level: 0 # no compression. Content is already a compress file + if-no-files-found: error + + - name: File release to end user + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: ${{ matrix.RELEASE_FILE_NAME }} diff --git a/.github/workflows/cicd_windows.yaml b/.github/workflows/cicd_windows.yaml new file mode 100644 index 0000000..05a2fba --- /dev/null +++ b/.github/workflows/cicd_windows.yaml @@ -0,0 +1,80 @@ +name: CI/CD on Windows systems. + +permissions: + contents: write + +on: + push: + pull_request: + workflow_dispatch: + # Automatic cron build every 6 months to check if everything still works. + schedule: + - cron: "0 0 1 1/6 *" + +jobs: + build: + runs-on: windows-latest + timeout-minutes: 60 + env: + RELEASE_ZIP_FILE: trackereditor_windows_amd64.zip + LAZ_OPT: + + steps: + - uses: actions/checkout@v6 + with: + submodules: true + + - name: Install Lazarus IDE + uses: ./.github/actions/install_lazarus + + - name: Download OpenSSL *.dll + run: | + # Need OpenSSL *.dll to download updated trackers from the internet. + # https://wiki.overbyte.eu/wiki/index.php/ICS_Download#Download_OpenSSL_Binaries + curl -L -O --output-dir enduser https://github.com/GerryFerdinandus/bittorrent-tracker-editor/releases/download/V1.32.0/libssl-3-x64.dll + curl -L -O --output-dir enduser https://github.com/GerryFerdinandus/bittorrent-tracker-editor/releases/download/V1.32.0/libcrypto-3-x64.dll + shell: bash + + - name: Build Release version + # Build trackereditor project (Release mode) + run: lazbuild --build-all --build-mode=Release ${{ env.LAZ_OPT }} source/project/tracker_editor/trackereditor.lpi + shell: bash + + - name: Build Unit Test on Windows + # Build unit test project (Debug mode) + run: lazbuild --build-all --build-mode=Debug ${{ env.LAZ_OPT }} source/project/unit_test/tracker_editor_test.lpi + shell: bash + + - name: Run Unit Test on Windows + # Also remove all the extra file created by test. + # We do not what it in the ZIP release files. + # Undo all changes made by testing. + run: | + set -e + enduser/test_trackereditor -a --format=plain + set +e + + # remove file created by unit test + rm -f enduser/console_log.txt + rm -f enduser/export_trackers.txt + git reset --hard + shell: bash + + - name: Create a zip file for Windows release. + run: | + Compress-Archive -Path enduser\*.txt, enduser\trackereditor.exe, enduser\*.dll -DestinationPath $Env:RELEASE_ZIP_FILE + shell: pwsh + + - name: Upload Artifact + uses: actions/upload-artifact@v6 + with: + name: artifact-${{ runner.os }} + path: ${{ env.RELEASE_ZIP_FILE }} + compression-level: 0 # no compression. Content is already a zip file + if-no-files-found: error + + - name: File release to end user + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: ${{ env.RELEASE_ZIP_FILE }} diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml index f6edf1f..2a77953 100644 --- a/.github/workflows/snap.yml +++ b/.github/workflows/snap.yml @@ -11,6 +11,18 @@ on: jobs: build: runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + with: + submodules: true + - uses: snapcore/action-build@v1 + + - name: Upload Artifact + uses: actions/upload-artifact@v6 + with: + name: artifact-snap + path: "*.snap" + compression-level: 0 # no compression. Content is already a zip file + if-no-files-found: error diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..adcb0d7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodule/dcpcrypt"] + path = submodule/dcpcrypt + url = https://git.code.sf.net/p/lazarus-ccr/dcpcrypt diff --git a/README.md b/README.md index 863f3c3..98779fe 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,9 @@ This software works on Windows 7+, macOS and Linux. ## Build Status: ## Continuous integration|Status| Generate an executable file for the operating system| Download link ------------|---------|---------|---------- -GitHub Actions |[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/gerryferdinandus/bittorrent-tracker-editor/cicd.yml)](https://github.com/GerryFerdinandus/bittorrent-tracker-editor/actions/workflows/cicd.yml)|Linux(amd64), macOS(Intel processors) and Windows|[![GitHub Latest release](https://img.shields.io/github/release/GerryFerdinandus/bittorrent-tracker-editor/all.svg)](https://github.com/GerryFerdinandus/bittorrent-tracker-editor/releases) -GitHub Actions (Ubuntu snap) |[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/gerryferdinandus/bittorrent-tracker-editor/snap.yml)](https://github.com/GerryFerdinandus/bittorrent-tracker-editor/actions/workflows/snap.yml)|Linux (amd64, arm64 and armhf)|[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/bittorrent-tracker-editor) +GitHub Actions |[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/gerryferdinandus/bittorrent-tracker-editor/cicd_ubuntu.yaml?label=Ubuntu)](https://github.com/GerryFerdinandus/bittorrent-tracker-editor/actions/workflows/cicd_ubuntu.yaml)[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/gerryferdinandus/bittorrent-tracker-editor/cicd_windows.yaml?label=Windows)](https://github.com/GerryFerdinandus/bittorrent-tracker-editor/actions/workflows/cicd_windows.yaml)[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/gerryferdinandus/bittorrent-tracker-editor/cicd_macos.yaml?label=macOS)](https://github.com/GerryFerdinandus/bittorrent-tracker-editor/actions/workflows/cicd_macos.yaml)|Linux(amd64), Windows(amd64) and macOS(Universal)|[![GitHub Latest release](https://img.shields.io/github/release/GerryFerdinandus/bittorrent-tracker-editor/all.svg)](https://github.com/GerryFerdinandus/bittorrent-tracker-editor/releases) +GitHub Actions (Ubuntu snap) |[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/gerryferdinandus/bittorrent-tracker-editor/snap.yml)](https://github.com/GerryFerdinandus/bittorrent-tracker-editor/actions/workflows/snap.yml)|Linux (amd64 and arm64)|[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-white.svg)](https://snapcraft.io/bittorrent-tracker-editor) +Flathub build server||Linux (amd64 and arm64)|Download on Flathub --- ## Warning: ## @@ -47,6 +48,8 @@ There is no backup function in this software. Use it at your own risk. Bittorren --- ## Software history: ## +### 1.33.1 ### + * FIX: Cannot open torrent file V2 format. ([Issue 51](https://github.com/GerryFerdinandus/bittorrent-tracker-editor/issues/51)) ### 1.33.0 ### * ADD: Support for OpenSSL 3 diff --git a/enduser/macos/app/trackereditor.app/Contents/Info.plist b/enduser/macos/app/trackereditor.app/Contents/Info.plist deleted file mode 100644 index 36c8919..0000000 --- a/enduser/macos/app/trackereditor.app/Contents/Info.plist +++ /dev/null @@ -1,47 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - trackereditor - CFBundleName - trackereditor - CFBundleIdentifier - com.company.trackereditor - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - APPL - CFBundleSignature - trac - CFBundleShortVersionString - 0.1 - CFBundleVersion - 1 - CSResourcesFileMapped - - CFBundleDocumentTypes - - - CFBundleTypeRole - Viewer - CFBundleTypeExtensions - - * - - CFBundleTypeOSTypes - - fold - disk - **** - - - - CFBundleIconFile - iconfile - NSHighResolutionCapable - - - diff --git a/enduser/macos/app/trackereditor.app/Contents/MacOS/.gitkeep b/enduser/macos/app/trackereditor.app/Contents/MacOS/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/enduser/macos/app/trackereditor.app/Contents/PkgInfo b/enduser/macos/app/trackereditor.app/Contents/PkgInfo deleted file mode 100644 index 6f749b0..0000000 --- a/enduser/macos/app/trackereditor.app/Contents/PkgInfo +++ /dev/null @@ -1 +0,0 @@ -APPL???? diff --git a/enduser/macos/app/trackereditor.app/Contents/Resources/iconfile.icns b/enduser/macos/app/trackereditor.app/Contents/Resources/iconfile.icns deleted file mode 100644 index 3120700..0000000 Binary files a/enduser/macos/app/trackereditor.app/Contents/Resources/iconfile.icns and /dev/null differ diff --git a/enduser/macos/entitlements.plist b/enduser/macos/entitlements.plist deleted file mode 100644 index a046386..0000000 --- a/enduser/macos/entitlements.plist +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - - - diff --git a/enduser/version.txt b/enduser/version.txt index 512f784..5b91cbf 100644 --- a/enduser/version.txt +++ b/enduser/version.txt @@ -1,3 +1,7 @@ +------ Version 1.33.1 +FIX: Cannot open torrent file V2 format (Issue 51) +Compiler Lazarus: v3.6 + ------ Version 1.33 ADD: Verify the working status of public trackers. (Issue 21) ADD: Wrong tracker URL format from torrent files should be unselected by default (Issue 22) diff --git a/metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.desktop b/metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.desktop new file mode 100644 index 0000000..798d0ca --- /dev/null +++ b/metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=bittorrent-tracker-editor +Comment=Add or remove tracker from torrent files. +Categories=Utility;Qt; +Icon=io.github.gerryferdinandus.bittorrent-tracker-editor +Exec=trackereditor +Terminal=false diff --git a/metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.metainfo.xml b/metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.metainfo.xml new file mode 100644 index 0000000..e2aa6d8 --- /dev/null +++ b/metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.metainfo.xml @@ -0,0 +1,78 @@ + + + io.github.gerryferdinandus.bittorrent-tracker-editor + bittorrent-tracker-editor + Add or remove tracker from torrent files. + Gerry Ferdinandus + io.github.gerryferdinandus.bittorrent-tracker-editor.desktop + + CC0-1.0 + MIT + + https://github.com/GerryFerdinandus/bittorrent-tracker-editor + https://github.com/GerryFerdinandus/bittorrent-tracker-editor/issues + + + + +

+ Warning: There is no backup function in this software. Use it at your own risk. +

+
    +
  • Select one torrent file or a folder with torrent files
  • +
  • Add one or more trackers at the same time
  • +
  • Remove one or more trackers at the same time
  • +
  • Remove all the trackers to create trackerless torrent. DHT torrent
  • +
  • Change public/private flag. Warning: This will change the torrent info HASH
  • +
  • Show torrent files content
  • +
  • Download stable trackers from newTrackon or ngosang
  • +
+

+ BitTorrent works fine without this program. You probably don't need this software. +

+
+ + + + The main window + + https://github.com/GerryFerdinandus/bittorrent-tracker-editor/releases/download/V1.32.0/trackereditor_list_linux.png + + + Torrent info + + https://github.com/GerryFerdinandus/bittorrent-tracker-editor/releases/download/V1.32.0/trackereditor_info_linux.png + + + + + + +

This release fixes the following bugs:

+
    +
  • Cannot open torrent file V2 format
  • +
+
+
+ + +

+ This release adds the following features: +

+
    +
  • Verify the working status of public trackers.
  • +
  • Direct download support for ngosang via menu.
  • +
  • Extra tabpage 'private torrent'.
  • +
  • Support 'Info Source' tag for private trackers
  • +
  • Wrong tracker URL format from torrent files should be unselected by default
  • +
  • Upload trackers to newTrackon
  • +
+

This release fixes the following bugs:

+
    +
  • support for '/announce.php'
  • +
  • WebTorrent do not have '/announce'
  • +
+
+
+
+
\ No newline at end of file diff --git a/snap/gui/bittorrent-tracker-editor.png b/metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.png similarity index 100% rename from snap/gui/bittorrent-tracker-editor.png rename to metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.png diff --git a/snap/gui/bittorrent-tracker-editor.desktop b/snap/gui/bittorrent-tracker-editor.desktop deleted file mode 100644 index a6cc131..0000000 --- a/snap/gui/bittorrent-tracker-editor.desktop +++ /dev/null @@ -1,10 +0,0 @@ -[Desktop Entry] -Version=1.0 -Name=Bittorrent tracker editor -Comment=Software for add or remove tracker from torrent files. -Exec=bittorrent-tracker-editor -Icon=${SNAP}/meta/gui/bittorrent-tracker-editor.png -Terminal=false -Type=Application -Categories=Utility; -Keywords=bittorrent;tracker;editor; diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 7ee68be..bc6fe21 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,61 +1,88 @@ name: bittorrent-tracker-editor -version: '1.33.0' -base: core22 -summary: Add or remove tracker from torrent files. -description: | - Features: - - Select one torrent file or a folder with torrent files. - - Add one or more trackers at the same time. - - Remove one or more trackers at the same time. - - Remove all the trackers to create trackerless torrent. DHT torrent - - Change public/private flag. Warning: This will change the torrent info HASH. - - Show torrent files content. - - Download stable trackers from newTrackon or ngosang +adopt-info: mainprogram +icon: metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.png + +base: core24 grade: stable confinement: strict -architectures: - - build-on: amd64 - - build-on: arm64 +platforms: + amd64: + arm64: apps: bittorrent-tracker-editor: + desktop: io.github.gerryferdinandus.bittorrent-tracker-editor.desktop extensions: - - kde-neon - command: app/trackereditor - environment: - # Fallback to XWayland if running in a Wayland session. - DISABLE_WAYLAND: 1 + - kde-neon-6 + command: trackereditor plugs: - home - network - removable-media - - pulseaudio parts: - bittorrent-tracker-editor: - source: https://github.com/GerryFerdinandus/bittorrent-tracker-editor.git + build_lazarus: + source: . plugin: nil - override-build: | - snapcraftctl build - lazbuild --build-mode=Release --widgetset=qt5 source/project/tracker_editor/trackereditor.lpi - mkdir $CRAFT_PART_INSTALL/app - mv enduser/trackereditor $CRAFT_PART_INSTALL/app build-packages: + - curl + - build-essential - fpc - - fpc-source - - lcl-nogui - - lazarus - - libqt5pas-dev + - libxkbcommon-dev - libQt5Pas: - plugin: nil - stage-packages: - - libqt5pas1 - prime: - - usr/lib/*/libQt5Pas.* + build-environment: + - LAZARUS_URL_TAR_GZ: "https://github.com/GerryFerdinandus/bittorrent-tracker-editor/releases/download/V1.32.0/lazarus.tar.gz" + - LAZARUS_QT_VERSION: "6" + - LIB_DIR: "/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR" + - LAZARUS_DIR: "$PWD/lazarus" + override-build: | + #Download lazarus source code. Directory 'lazarus' will be created in the root. + curl -L -O $LAZARUS_URL_TAR_GZ + tar -xzf *.tar.gz + + #--- Create libQTpas.so and put it in /usr/lib/ and $CRAFT_PART_INSTALL + cd "$LAZARUS_DIR/lcl/interfaces/qt${LAZARUS_QT_VERSION}/cbindings/" + /snap/kde-qt6-core24-sdk/current/usr/bin/qt6/qmake6 + make -j$(nproc) + + # copy the libQTpas.so to /usr/lib/ (needed for compile-time linking) + cp -av libQt${LAZARUS_QT_VERSION}Pas.* $LIB_DIR/ -# Only 2 files are explicitly added in this snap: trackereditor + libQt5Pas.so -# No need to add a cleanup process. There's nothing to clean up. + # copy the libQTpas.so to snap install directory (needed for run-time linking) + cp -av --parents $LIB_DIR/libQt${LAZARUS_QT_VERSION}Pas.* $CRAFT_PART_INSTALL + + #--- Make lazbuild and put the link with extra parameter in /usr/bin/ + cd "$LAZARUS_DIR" + make lazbuild + echo "$LAZARUS_DIR/lazbuild --primary-config-path=$LAZARUS_DIR --lazarusdir=$LAZARUS_DIR \$*" > /usr/bin/lazbuild + chmod 777 /usr/bin/lazbuild + + mainprogram: # Build and add to snap the main program: trackereditor + after: [build_lazarus] + source: . + plugin: nil + parse-info: [metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.metainfo.xml] + override-build: | + lazbuild --build-mode=Release --widgetset=qt6 source/project/tracker_editor/trackereditor.lpi + install enduser/trackereditor $CRAFT_PART_INSTALL/ + install metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.desktop $CRAFT_PART_INSTALL/ + +# -------------------------------------------------------------- +# Only 3 files are explicitly added in this snap +# - main program: enduser/trackereditor +# - desktop file: metainfo/io.github.gerryferdinandus.bittorrent-tracker-editor.desktop +# - libQt6Pas.so created during the build_lazarus part +# +# Create snap. Run from the project root folder: +# snapcraft pack --verbosity verbose +# +# To look what is inside the snap file. Directory 'squashfs-root' will be created in the root folder: +# unsquashfs *.snap +# +# Install the snap: +# sudo snap install --devmode ./*.snap # -# Use: 'unsquashfs *.snap' to look what is inside the snap file. +# Run the snap +# snap run bittorrent-tracker-editor +# -------------------------------------------------------------- diff --git a/source/code/controler_treeview_torrent_data.pas b/source/code/controler_treeview_torrent_data.pas index 99ab014..93fa146 100644 --- a/source/code/controler_treeview_torrent_data.pas +++ b/source/code/controler_treeview_torrent_data.pas @@ -70,7 +70,7 @@ implementation TORRENT_FILES_CONTENTS_FORM_CAPTION = 'Show all the files inside the torrents. (Use right mouse for popup menu.)'; -{ Tcontroler_treeview_torrent_data } + { Tcontroler_treeview_torrent_data } procedure Tcontroler_treeview_torrent_data.FillThePopupMenu; begin @@ -285,16 +285,16 @@ procedure Tcontroler_treeview_torrent_data.AddOneTorrentFileDecoded( DecodeTorrent: TDecodeTorrent); var CountFiles: integer; - TorrentFileNameStr, TrackerStr: UTF8String; + TorrentFileNameStr, TrackerStr: utf8string; TreeNodeTorrent, TreeNodeFiles, TreeNodeTrackers, TreeNodeInfo: TTreeNode; - begin //--------------------- Fill the treeview with torrent files TorrentFileNameStr := ExtractFileName(DecodeTorrent.FilenameTorrent); //Add the torrent file name + size of all the files combined. - TorrentFileNameStr := TorrentFileNameStr + ' SIZE: ' + + TorrentFileNameStr := TorrentFileNameStr + ' (Version: ' + + DecodeTorrent.TorrentVersionToString + ') SIZE: ' + ByteSizeToBiggerSizeFormatStr(DecodeTorrent.TotalFileSize) + ' Files: ' + IntToStr(DecodeTorrent.InfoFilesCount) + '' + ' Tracker: ' + IntToStr(DecodeTorrent.TrackerList.Count) + ''; @@ -305,7 +305,9 @@ procedure Tcontroler_treeview_torrent_data.AddOneTorrentFileDecoded( TorrentFileNameStr); //Without directory path //must be in this order (Files, Trackers, Info) - TreeNodeFiles := FTreeViewFileContents.Items.AddChild(TreeNodeTorrent, 'Files'); + TreeNodeFiles := FTreeViewFileContents.Items.AddChild(TreeNodeTorrent, + 'Files V' + IntToStr(DecodeTorrent.InfoFilesVersion)); + TreeNodeTrackers := FTreeViewFileContents.Items.AddChild(TreeNodeTorrent, 'Trackers'); TreeNodeInfo := FTreeViewFileContents.Items.AddChild(TreeNodeTorrent, 'Info'); @@ -340,8 +342,10 @@ procedure Tcontroler_treeview_torrent_data.AddOneTorrentFileDecoded( FTreeViewFileContents.Items.AddChild(TreeNodeInfo, 'Comment: ' + DecodeTorrent.Comment); - FTreeViewFileContents.Items.AddChild(TreeNodeInfo, 'Info Hash: ' + - DecodeTorrent.InfoHash); + FTreeViewFileContents.Items.AddChild(TreeNodeInfo, 'Info Hash V1: ' + + DecodeTorrent.InfoHash_V1); + FTreeViewFileContents.Items.AddChild(TreeNodeInfo, 'Info Hash V2: ' + + DecodeTorrent.InfoHash_V2); FTreeViewFileContents.Items.AddChild(TreeNodeInfo, 'Created On: ' + DateTimeToStr(DecodeTorrent.CreatedDate)); FTreeViewFileContents.Items.AddChild(TreeNodeInfo, 'Created By: ' + @@ -363,6 +367,15 @@ procedure Tcontroler_treeview_torrent_data.AddOneTorrentFileDecoded( DecodeTorrent.InfoSource); end; + if DecodeTorrent.MetaVersion > 0 then + begin // 'meta version'is in torrent file present + FTreeViewFileContents.Items.AddChild(TreeNodeInfo, 'Meta Version: ' + + IntToStr(DecodeTorrent.MetaVersion)); + end; + + FTreeViewFileContents.Items.AddChild(TreeNodeInfo, 'Padding (beb 47): ' + + DecodeTorrent.PaddingToString); + //All the files count inside the torrent must be added to FTotalFileInsideTorrent Inc(FTotalFileInsideTorrent, DecodeTorrent.InfoFilesCount); diff --git a/source/code/controlergridtorrentdata.pas b/source/code/controlergridtorrentdata.pas index 1f81c41..a885460 100644 --- a/source/code/controlergridtorrentdata.pas +++ b/source/code/controlergridtorrentdata.pas @@ -28,15 +28,17 @@ TControlerGridTorrentData = class //The collumn must be in this design order. FTorrentFile, //0 FInfoFileName, //1 - FInfoHash, //2 - FCreatedOn, //3 - FCreatedBy, //4 - FComment, //5 - FPrivateTorrent, //6 - FInfoSource, //7 - FPieceLength, //8 - FTotaSize, //9 - FIndexOrder //10 + FTorrentVersion, //2 + FPadding, //3 + FInfoHash, //4 + FCreatedOn, //5 + FCreatedBy, //6 + FComment, //7 + FPrivateTorrent, //8 + FInfoSource, //9 + FPieceLength, //10 + FTotaSize, //11 + FIndexOrder //12 : TGridColumn; FRowIsMovedNeedUpdate: boolean; @@ -49,15 +51,17 @@ TControlerGridTorrentData = class //All the string that can be written to grid. TorrentFile, //0 InfoFileName, //1 - InfoHash, //2 - CreatedOn, //3 - CreatedBy, //4 - Comment, //5 - PrivateTorrent, //6 - InfoSource, //7 - PieceLength, //8 - TotaSize, //9 - IndexOrder //10 + TorrentVersion, //2 + Padding, //3 + InfoHash, //4 + CreatedOn, //5 + CreatedBy, //6 + Comment, //7 + PrivateTorrent, //8 + InfoSource, //9 + PieceLength, //10 + TotaSize, //11 + IndexOrder //12 : UTF8String; procedure ClearAllImageIndex; @@ -135,6 +139,8 @@ procedure TControlerGridTorrentData.AppendRow; //write all the string to the cell. WriteCell(FTorrentFile, TorrentFile); WriteCell(FInfoFileName, InfoFileName); + WriteCell(FTorrentVersion, TorrentVersion); + WriteCell(FPadding, Padding); WriteCell(FInfoHash, InfoHash); WriteCell(FCreatedOn, CreatedOn); WriteCell(FCreatedBy, CreatedBy); @@ -183,15 +189,17 @@ constructor TControlerGridTorrentData.Create(StringGridTorrentData: TStringGrid) //Track the column AddColumn(FTorrentFile, 0); AddColumn(FInfoFileName, 1); - AddColumn(FInfoHash, 2); - AddColumn(FCreatedOn, 3); - AddColumn(FCreatedBy, 4); - AddColumn(FComment, 5); - AddColumn(FPrivateTorrent, 6); - AddColumn(FInfoSource, 7); - AddColumn(FPieceLength, 8); - AddColumn(FTotaSize, 9); - AddColumn(FIndexOrder, 10); + AddColumn(FTorrentVersion, 2); + AddColumn(FPadding, 3); + AddColumn(FInfoHash, 4); + AddColumn(FCreatedOn, 5); + AddColumn(FCreatedBy, 6); + AddColumn(FComment, 7); + AddColumn(FPrivateTorrent, 8); + AddColumn(FInfoSource, 9); + AddColumn(FPieceLength, 10); + AddColumn(FTotaSize, 11); + AddColumn(FIndexOrder, 12); //Fillin the tag value UpdateColumnTag; diff --git a/source/code/decodetorrent.pas b/source/code/decodetorrent.pas index d035540..56b3b70 100644 --- a/source/code/decodetorrent.pas +++ b/source/code/decodetorrent.pas @@ -17,15 +17,16 @@ interface uses - Classes, SysUtils, contnrs, LazUTF8Classes, BEncode; + Classes, SysUtils, contnrs, BEncode; type - //Every torrent file have one or more files. - TDecodeTorrentFile = record - Filename: utf8string; - FileLength: int64; - end; + TTorrentVersion = ( + tv_V1, //< V1 version + tv_V2, //< V2 version + tv_Hybrid, //< V1 + V2 hybrid + tv_unknown //< Can not decode it + ); TDecodeTorrentFileNameAndLength = class Filename: utf8string; @@ -36,30 +37,42 @@ TDecodeTorrentFileNameAndLength = class { TDecodeTorrent } TDecodeTorrent = class private - FFilenameTorrent: UTF8String; + FFilenameTorrent: utf8string; FMemoryStream: TMemoryStream; FBEncoded: TBEncoded; FObjectListFileNameAndLength: TObjectList; FTotalFileSize: int64; + FTorrentVersion: TTorrentVersion; //Torrent file must have 'info' item. FBEncoded_Info: TBEncoded; FBEncoded_Comment: TBEncoded; - FInfoHash: utf8string; + FInfoHash_V1: utf8string; + FInfoHash_V2: utf8string; FCreatedBy: utf8string; FCreatedDate: TDateTime; FComment: utf8string; FInfoSource: utf8string; FName: utf8string; FPieceLenght: int64; + FMetaVersion: int64; FPrivateTorrent: boolean; + FPaddingPresent_V1: boolean; + FPaddingPresent_V2: boolean; + FInfoFilesVersion: integer; function DecodeTorrent: boolean; overload; + function isPresentInfoFiles_V1: boolean; + function isPresentInfoFileTree_V2: boolean; + + function GetSha256(const Source: utf8string): utf8string; + function GetMetaVersion: int64; function GetAnnounceList: boolean; - function GetFileList: boolean; - function GetInfoHash: utf8string; + function GetFileList_V1: boolean; + function GetFileList_V2: boolean; + function GetOneFileTorrent: boolean; function GetCreatedBy: utf8string; function GetCreatedDate: TDateTime; function GetComment: utf8string; @@ -72,14 +85,18 @@ TDecodeTorrent = class //All the trackers inside this torrent file TrackerList: TStringList; - property FilenameTorrent: UTF8String read FFilenameTorrent; + property MetaVersion: int64 read FMetaVersion; + property TorrentVersion: TTorrentVersion read FTorrentVersion; + property PaddingPresent_V1: boolean read FPaddingPresent_V1; + property PaddingPresent_V2: boolean read FPaddingPresent_V2; + + property FilenameTorrent: utf8string read FFilenameTorrent; - //Every torrent file have one or more files. - // TorrentFilesArray: Array of TDecodeTorrentFile; property TotalFileSize: int64 read FTotalFileSize; //Info hash - property InfoHash: utf8string read FInfoHash; + property InfoHash_V1: utf8string read FInfoHash_V1; + property InfoHash_V2: utf8string read FInfoHash_V2; //Created by property CreatedBy: utf8string read FCreatedBy; @@ -98,24 +115,28 @@ TDecodeTorrent = class //public/private flag property PrivateTorrent: boolean read FPrivateTorrent; - procedure RemovePrivateTorrentFlag; - procedure AddPrivateTorrentFlag; + function RemovePrivateTorrentFlag: boolean; + function AddPrivateTorrentFlag: boolean; //info.source property InfoSource: utf8string read FInfoSource; - procedure InfoSourceRemove; - procedure InfoSourceAdd(const Value: utf8string); + function InfoSourceRemove: boolean; + function InfoSourceAdd(const Value: utf8string): boolean; - //info.files + //info.files and info. file tree + property InfoFilesVersion: integer read FInfoFilesVersion; function InfoFilesCount: integer; function InfoFilesNameIndex(index: integer): utf8string; function InfoFilesLengthIndex(index: integer): int64; //Announce list - procedure RemoveAnnounce; - procedure RemoveAnnounceList; - procedure ChangeAnnounce(const TrackerURL: utf8string); - procedure ChangeAnnounceList(StringList: TStringList); + function RemoveAnnounce: boolean; + function RemoveAnnounceList: boolean; + function ChangeAnnounce(const TrackerURL: utf8string): boolean; + function ChangeAnnounceList(StringList: TStringList): boolean; + + function TorrentVersionToString: utf8string; + function PaddingToString: utf8string; //Load torrent file function DecodeTorrent(const Filename: utf8string): boolean; overload; @@ -131,7 +152,7 @@ TDecodeTorrent = class implementation -uses dateutils, SHA1, FileUtil, LazUTF8; +uses dateutils, SHA1, DCPsha256, FileUtil, LazUTF8; function SortFileName(Item1, Item2: Pointer): integer; begin @@ -149,7 +170,7 @@ function Sort_(Item1, Item2: Pointer): integer; constructor TDecodeTorrent.Create; begin inherited; - //Every torrent have + //Every torrent have file list FObjectListFileNameAndLength := TObjectList.Create; //List for all the trackers. @@ -177,13 +198,13 @@ destructor TDecodeTorrent.Destroy; function TDecodeTorrent.DecodeTorrent(const Filename: utf8string): boolean; var - S: TFileStreamUtf8; + S: TFileStream; begin FFilenameTorrent := Filename; //Load torrent file in FMemoryStream. This will be process by DecodeTorrent(); try //Support FilenameTorrent with unicode. - S := TFileStreamUtf8.Create(FilenameTorrent, fmOpenRead or fmShareDenyWrite); + S := TFileStream.Create(FilenameTorrent, fmOpenRead or fmShareDenyWrite); try FMemoryStream.LoadFromStream(S); finally @@ -212,7 +233,7 @@ function TDecodeTorrent.GetAnnounceList: boolean; var TempBEncoded: TBEncoded; i, Count: integer; - TrackerStr: UTF8String; + TrackerStr: utf8string; begin //return false, if crash at decoding. Announce is optional in torrent file. TrackerList.Clear; @@ -254,90 +275,176 @@ function TDecodeTorrent.GetAnnounceList: boolean; end; end; -function TDecodeTorrent.GetFileList: boolean; +function TDecodeTorrent.GetFileList_V1: boolean; var - //TempBEncodedInfo, - TempBEncodedInfoFiles, TempBEncodedInfoFilesPath: TBEncoded; - TempBEncodedInfoFilesData: TBEncodedData; - - i, x, countFiles, countPath: integer; - FilenameWithPathStr, Filename: utf8string; - - DecodeTorrentFileName: TDecodeTorrentFileNameAndLength; + P_File: Pointer; + P_Item: Pointer; + P_Path: Pointer; FileLength: int64; + InfoFiles: TBEncoded; + NodeData: TBEncodedData; + ThisIsFileWithPadding: boolean; + FilenameWithPathStr: utf8string; + DecodeTorrentFileName: TDecodeTorrentFileNameAndLength; begin -{ info/files/path -> all the files names - or - info/name -> one file name only -} - //return false if there is no file at all. This must not be posible. - + // return false if there is no file at all. This must not be posible. + // info/files/path -> all the files names + FInfoFilesVersion := 1; FObjectListFileNameAndLength.Clear; FTotalFileSize := 0; - try + try {find 'info.files' } - TempBEncodedInfoFiles := FBEncoded_Info.ListData.FindElement('files'); + InfoFiles := FBEncoded_Info.ListData.FindElement('files'); - if assigned(TempBEncodedInfoFiles) then + if assigned(InfoFiles) then begin //'info.files' found - countFiles := TempBEncodedInfoFiles.ListData.Count; - if countFiles > 0 then - begin - for i := 0 to countFiles - 1 do - begin - //Get the info.files node. - TempBEncodedInfoFilesData := TempBEncodedInfoFiles.ListData.Items[i]; - - //Get the file name with path - FilenameWithPathStr := ''; - TempBEncodedInfoFilesPath := - TempBEncodedInfoFilesData.Data.ListData.FindElement('path'); - countPath := TempBEncodedInfoFilesPath.ListData.Count; - for x := 0 to countPath - 1 do + + for P_File in InfoFiles.ListData do + begin // Every file need to be process one by one + + ThisIsFileWithPadding := False; + for P_Item in TBEncodedData(P_File).Data.ListData do + begin // Every item inside the file must be process one by one + NodeData := TBEncodedData(P_Item); + + // Get the file name with path + if NodeData.Header = 'path' then + begin + FilenameWithPathStr := ''; + for P_Path in NodeData.Data.ListData do + begin + FilenameWithPathStr := + FilenameWithPathStr + DirectorySeparator + TBEncodedData( + P_Path).Data.StringData; + end; + Continue; + end; + + // Get the file length + if NodeData.Header = 'length' then begin - FilenameWithPathStr := - FilenameWithPathStr + DirectorySeparator + - TempBEncodedInfoFilesPath.ListData.Items[x].Data.StringData; + FileLength := NodeData.Data.IntegerData; + Continue; end; + // check if this is padding + if NodeData.Header = 'attr' then + begin + ThisIsFileWithPadding := UTF8Pos('p', NodeData.Data.StringData) > 0; + if ThisIsFileWithPadding and not FPaddingPresent_V1 then + begin + FPaddingPresent_V1 := True; + end; + end; + end; // Every item inside the file - //Get the file length - FileLength := TempBEncodedInfoFilesData.Data.ListData.FindElement( - 'length').IntegerData; + // one file is decoded. Now add it to the list + DecodeTorrentFileName := TDecodeTorrentFileNameAndLength.Create; + DecodeTorrentFileName.Filename := FilenameWithPathStr; + DecodeTorrentFileName.FileLength := FileLength; + FObjectListFileNameAndLength.Add(DecodeTorrentFileName); - DecodeTorrentFileName := TDecodeTorrentFileNameAndLength.Create; - DecodeTorrentFileName.Filename := FilenameWithPathStr; - DecodeTorrentFileName.FileLength := FileLength; - FObjectListFileNameAndLength.Add(DecodeTorrentFileName); - //add it to the total sum of all files inside the torrent. + //add FileLength to the total sum of all files inside the torrent. + if not ThisIsFileWithPadding then + begin Inc(FTotalFileSize, FileLength); end; + end; // Every file need to be process one by one + end; //'info.files' found + + //There is file found inside the torrent? + Result := FObjectListFileNameAndLength.Count > 0; + + except + //Can not found items that should be present. + Result := False; + end; + +end; + +function TDecodeTorrent.GetFileList_V2: boolean; +var + BEncodedFileTree: TBEncoded; + + procedure ProcessPathOrFile(const Node: TBEncoded; const path: string); + var + P_Item: Pointer; + FileLength: int64; + NodeData: TBEncodedData; + ThisIsFileWithPadding: boolean; + DecodeTorrentFileName: TDecodeTorrentFileNameAndLength; + begin + // Everyting in the tree is befDictionary + ThisIsFileWithPadding := False; + FileLength := -1; // -1 is no file length found yet. + + + for P_Item in node.ListData do + begin // read all the befDictionary list items one by one + NodeData := TBEncodedData(P_Item); + + // Found a new path node + if NodeData.Data.Format = befDictionary then + begin + ProcessPathOrFile(NodeData.Data, path + DirectorySeparator + NodeData.Header); + Continue; end; - end - else //there is no 'info.files' found. This is an 'one file' torrent. - begin// Look for'info.name' and 'info.length' - //Get the file name - Filename := FBEncoded_Info.ListData.FindElement('name').StringData; - FileLength := FBEncoded_Info.ListData.FindElement('length').IntegerData; + // check if this present dictionary is a padding node + if (NodeData.Data.Format = befString) and (NodeData.Header = 'attr') then + begin + ThisIsFileWithPadding := (UTF8Pos('p', NodeData.Data.StringData) > 0); + if ThisIsFileWithPadding and not FPaddingPresent_V2 then + begin + FPaddingPresent_V2 := True; + end; + Continue; + end; + // Found a file node? This is at the end of the tree node. + if (NodeData.Data.Format = befInteger) and (NodeData.Header = 'length') then + begin + FileLength := NodeData.Data.IntegerData; + end; + + end; // read all the befDictionary list items one by one + + // One dictionary list have been process + // Is this a dictionary list with a file inside? + if FileLength >= 0 then + begin DecodeTorrentFileName := TDecodeTorrentFileNameAndLength.Create; - DecodeTorrentFileName.Filename := Filename; + DecodeTorrentFileName.Filename := ExtractFileDir(path); DecodeTorrentFileName.FileLength := FileLength; FObjectListFileNameAndLength.Add(DecodeTorrentFileName); - - Inc(FTotalFileSize, FileLength); + //add FileLength to the total sum of all files inside the torrent. + if not ThisIsFileWithPadding then + begin + Inc(FTotalFileSize, DecodeTorrentFileName.FileLength); + end; end; + end; +begin + Result := False; + FInfoFilesVersion := 2; + FObjectListFileNameAndLength.Clear; + FTotalFileSize := 0; + try + {find 'info.file tree' } + BEncodedFileTree := FBEncoded_Info.ListData.FindElement('file tree'); - //There is file found inside the torrent. + if assigned(BEncodedFileTree) then + begin //'info.file tree' found + ProcessPathOrFile(BEncodedFileTree, ''); + end; + + //There is file found inside the torrent? Result := FObjectListFileNameAndLength.Count > 0; - //We prefer that the file name are in sorted order. - FObjectListFileNameAndLength.Sort(@SortFileName); except //Can not found items that should be present. @@ -346,6 +453,32 @@ function TDecodeTorrent.GetFileList: boolean; end; +function TDecodeTorrent.GetOneFileTorrent: boolean; +var + Filename: utf8string; + DecodeTorrentFileName: TDecodeTorrentFileNameAndLength; + TempBEncoded: TBEncoded; +begin + try + // This is a torrent without file list/tree + TempBEncoded := FBEncoded_Info.ListData.FindElement('length'); + Result := assigned(TempBEncoded); + if Result then + begin + FInfoFilesVersion := 1; + FObjectListFileNameAndLength.Clear; + Filename := FBEncoded_Info.ListData.FindElement('name').StringData; + DecodeTorrentFileName := TDecodeTorrentFileNameAndLength.Create; + DecodeTorrentFileName.Filename := Filename; + DecodeTorrentFileName.FileLength := TempBEncoded.IntegerData; + FObjectListFileNameAndLength.Add(DecodeTorrentFileName); + FTotalFileSize := TempBEncoded.IntegerData; + end; + except + Result := False; + end; +end; + function TDecodeTorrent.DecodeTorrent: boolean; begin Result := False; @@ -356,20 +489,19 @@ function TDecodeTorrent.DecodeTorrent: boolean; FreeAndNil(FBEncoded); end; - //the torrent file inside FMemoryStream -> BEnencode it - FMemoryStream.Position := 0; - FBEncoded := TBEncoded.Create(FMemoryStream); - - - - - //Read the tracker list and file list inside the torrent file. - FTotalFileSize := 0; + // Clear List that will be filed later. TrackerList.Clear; - FObjectListFileNameAndLength.Clear; + + // Reset to default value FTotalFileSize := 0; + FTorrentVersion := tv_unknown; + FPaddingPresent_V1 := False; + FPaddingPresent_V2 := False; + //the torrent file inside FMemoryStream -> BEnencode it + FMemoryStream.Position := 0; + FBEncoded := TBEncoded.Create(FMemoryStream); //torrent file MUST begin with befDictionary. if FBEncoded.Format <> befDictionary then @@ -380,14 +512,39 @@ function TDecodeTorrent.DecodeTorrent: boolean; if not assigned(FBEncoded_Info) then exit; //error + // Is this V1,V2 or hybrid torrent type? + if isPresentInfoFiles_V1 then FTorrentVersion := tv_V1; + if isPresentInfoFileTree_V2 then + begin + if FTorrentVersion = tv_V1 then + FTorrentVersion := tv_Hybrid + else + FTorrentVersion := tv_V2; + end; //Accept torrent only when there is no issue in reading AnnounceList and file list - if GetAnnounceList and GetFileList then + if GetAnnounceList then begin - Result := True; + case FTorrentVersion of + tv_V1: Result := GetFileList_V1; + tv_V2: Result := GetFileList_V2; + tv_Hybrid: + begin // Only V2 is actualy used. V1 need to be read to look for padding. + Result := GetFileList_V1; + if Result then GetFileList_V2; + end; + else + Assert(False, 'Missing torrent version'); + end; + end; + + if not Result then + begin // There is nothing found in tree list. Maybe this is a Torrent with one file? + Result := GetOneFileTorrent; + if Result then FTorrentVersion := tv_V1; end; - FInfoHash := GetInfoHash; + // FInfoHash_V1 := GetInfoHash; FCreatedBy := GetCreatedBy; FCreatedDate := GetCreatedDate; FComment := GetComment; @@ -395,7 +552,91 @@ function TDecodeTorrent.DecodeTorrent: boolean; FPieceLenght := GetPieceLenght; FPrivateTorrent := GetPrivateTorrent; FInfoSource := GetInfoSource; + FMetaVersion := GetMetaVersion; + except + Result := False; + end; +end; + +function TDecodeTorrent.isPresentInfoFiles_V1: boolean; +var + Info: TBEncoded; + str: utf8string; +begin + try + {find 'info.files' } + Info := FBEncoded_Info.ListData.FindElement('files'); + Result := assigned(info); + if Result then + begin + str := ''; + TBEncoded.Encode(FBEncoded_Info, str); + FInfoHash_V1 := UpperCase(SHA1Print(SHA1String(str))); + end + else + begin + FInfoHash_V1 := 'N/A'; + end; + except + Result := False; + end; +end; + +function TDecodeTorrent.isPresentInfoFileTree_V2: boolean; +var + Info: TBEncoded; + str: utf8string; +begin + try + {find 'info.file tree' } + Info := FBEncoded_Info.ListData.FindElement('file tree'); + Result := assigned(info); + if Result then + begin + str := ''; + TBEncoded.Encode(FBEncoded_Info, str); + FInfoHash_V2 := UpperCase(GetSha256(str)); + end + else + begin + FInfoHash_V2 := 'N/A'; + end; except + Result := False; + end; +end; + +function TDecodeTorrent.GetSha256(const Source: utf8string): utf8string; +var + Hash: TDCP_sha256; + Digest: array[0..31] of byte; + i: integer; +begin + Digest[0] := 0; // suppres compiler warning. + Hash := TDCP_sha256.Create(nil); + Hash.Init; + Hash.UpdateStr(Source); + Hash.Final(Digest); + Result := ''; + for i := Low(Digest) to High(Digest) do + begin + Result := Result + IntToHex(Digest[i], 2); + end; + Hash.Free; +end; + +function TDecodeTorrent.GetMetaVersion: int64; +var + TempBEncoded: TBEncoded; +begin + Result := 0; + try + {find 'meta version' } + TempBEncoded := FBEncoded_Info.ListData.FindElement('meta version'); + if assigned(TempBEncoded) then + Result := TempBEncoded.IntegerData; + except + Result := 0; end; end; @@ -415,7 +656,6 @@ function TDecodeTorrent.InfoFilesLengthIndex(index: integer): int64; Result := TDecodeTorrentFileNameAndLength(FObjectListFileNameAndLength[index]).FileLength; end; - function TDecodeTorrent.GetPrivateTorrent: boolean; var TempBEncoded: TBEncoded; @@ -427,6 +667,7 @@ function TDecodeTorrent.GetPrivateTorrent: boolean; if assigned(TempBEncoded) then Result := TempBEncoded.IntegerData = 1; except + Result := False; end; end; @@ -441,6 +682,7 @@ function TDecodeTorrent.GetInfoSource: utf8string; if assigned(TempBEncoded) then Result := TempBEncoded.StringData; except + Result := ''; end; end; @@ -475,6 +717,7 @@ procedure TDecodeTorrent.SetComment(const AValue: utf8string); end; except + FComment := AValue; end; end; @@ -493,17 +736,19 @@ procedure TDecodeTorrent.SetComment(const AValue: utf8string); } -procedure TDecodeTorrent.RemovePrivateTorrentFlag; +function TDecodeTorrent.RemovePrivateTorrentFlag: boolean; begin try FBEncoded_Info.ListData.RemoveElement('private'); + Result := True; except + Result := False; end; //read databack again FPrivateTorrent := GetPrivateTorrent; end; -procedure TDecodeTorrent.AddPrivateTorrentFlag; +function TDecodeTorrent.AddPrivateTorrentFlag: boolean; var Encoded: TBEncoded; Data: TBEncodedData; @@ -517,23 +762,27 @@ procedure TDecodeTorrent.AddPrivateTorrentFlag; Data.Header := 'private'; FBEncoded_Info.ListData.Add(Data); FBEncoded_Info.ListData.Sort(@sort_);//text must be in alfabetical order. + Result := True; except + Result := False; end; //read databack again FPrivateTorrent := GetPrivateTorrent; end; -procedure TDecodeTorrent.InfoSourceRemove; +function TDecodeTorrent.InfoSourceRemove: boolean; begin try FBEncoded_Info.ListData.RemoveElement('source'); + Result := True; except + Result := False; end; //read databack again FInfoSource := GetInfoSource; end; -procedure TDecodeTorrent.InfoSourceAdd(const Value: utf8string); +function TDecodeTorrent.InfoSourceAdd(const Value: utf8string): boolean; var Encoded: TBEncoded; Data: TBEncodedData; @@ -547,38 +796,44 @@ procedure TDecodeTorrent.InfoSourceAdd(const Value: utf8string); Data.Header := 'source'; FBEncoded_Info.ListData.Add(Data); FBEncoded_Info.ListData.Sort(@sort_);//text must be in alfabetical order. + Result := True; except + Result := False; end; FInfoSource := GetInfoSource; end; -procedure TDecodeTorrent.RemoveAnnounce; +function TDecodeTorrent.RemoveAnnounce: boolean; begin try FBEncoded.ListData.RemoveElement('announce'); + Result := True; except + Result := False; end; end; -procedure TDecodeTorrent.RemoveAnnounceList; +function TDecodeTorrent.RemoveAnnounceList: boolean; begin try FBEncoded.ListData.RemoveElement('announce-list'); + Result := True; except + Result := False; end; end; function TDecodeTorrent.SaveTorrent(const Filename: utf8string): boolean; var str: utf8string; - S: TFileStreamUTF8; + S: TFileStream; begin try //Encode it to string format str := ''; TBEncoded.Encode(FBEncoded, str); //Write string to file. Support filename with unicode. - S := TFileStreamUTF8.Create(FileName, fmCreate); + S := TFileStream.Create(FileName, fmCreate); try Result := s.Write(Str[1], length(Str)) = length(Str); finally @@ -591,7 +846,7 @@ function TDecodeTorrent.SaveTorrent(const Filename: utf8string): boolean; -procedure TDecodeTorrent.ChangeAnnounce(const TrackerURL: utf8string); +function TDecodeTorrent.ChangeAnnounce(const TrackerURL: utf8string): boolean; var Encoded: TBEncoded; Data: TBEncodedData; @@ -605,19 +860,21 @@ procedure TDecodeTorrent.ChangeAnnounce(const TrackerURL: utf8string); Data.Header := 'announce'; FBEncoded.ListData.Add(Data); FBEncoded.ListData.Sort(@sort_);//text must be in alfabetical order. + Result := True; except + Result := False; end; end; -procedure TDecodeTorrent.ChangeAnnounceList(StringList: TStringList); +function TDecodeTorrent.ChangeAnnounceList(StringList: TStringList): boolean; var EncodedListRoot, EncodedList, EncodedString: TBEncoded; DataRootBEncodedData: TBEncodedData; i: integer; begin + Result := True; //remove the present one. RemoveAnnounceList; - //if there is nothing in the list then exit. if StringList.Count = 0 then Exit; @@ -652,17 +909,40 @@ procedure TDecodeTorrent.ChangeAnnounceList(StringList: TStringList); FBEncoded.ListData.Sort(@sort_);//text must be in alfabetical order. except + Result := False; end; end; -function TDecodeTorrent.GetInfoHash: utf8string; +function TDecodeTorrent.TorrentVersionToString: utf8string; begin - Result := ''; - try - //The info.value will be hash with SHA1 - TBEncoded.Encode(FBEncoded_Info, Result); - Result := UpperCase(SHA1Print(SHA1String(Result))); - except + case FTorrentVersion of + tv_V1: Result := 'V1'; + tv_V2: Result := 'V2'; + tv_Hybrid: Result := 'Hybrid (V1&V2)'; + tv_unknown: Result := 'unknown'; + else + Result := 'TorrentVersionToString: unkown value'; + end; +end; + +function TDecodeTorrent.PaddingToString: utf8string; +begin + case FTorrentVersion of + tv_V1: + begin + Result := BoolToStr(FPaddingPresent_V1, 'Yes', 'No'); + end; + tv_V2: + begin + Result := BoolToStr(FPaddingPresent_V2, 'Yes', 'No'); + end; + tv_Hybrid: + begin // Show only V2 hash. No space for both V1 and V2 + Result := 'V1:' + BoolToStr(FPaddingPresent_V1, 'Yes', 'No') + + ' V2:' + BoolToStr(FPaddingPresent_V2, 'Yes', 'No'); + end; + else + Result := 'N/A' end; end; @@ -675,8 +955,8 @@ function TDecodeTorrent.GetCreatedBy: utf8string; TempBEncoded := FBEncoded.ListData.FindElement('created by'); if assigned(TempBEncoded) then Result := TempBEncoded.StringData; - except + Result := ''; end; end; @@ -690,6 +970,7 @@ function TDecodeTorrent.GetCreatedDate: TDateTime; if assigned(TempBEncoded) then Result := UnixToDateTime(TempBEncoded.IntegerData); except + Result := 0; end; end; @@ -701,6 +982,7 @@ function TDecodeTorrent.GetComment: utf8string; if assigned(FBEncoded_Comment) then Result := UTF8Trim(FBEncoded_Comment.StringData); except + Result := ''; end; end; @@ -715,6 +997,7 @@ function TDecodeTorrent.GetName: utf8string; if assigned(TempBEncoded) then Result := TempBEncoded.StringData; except + Result := ''; end; end; @@ -729,6 +1012,7 @@ function TDecodeTorrent.GetPieceLenght: int64; if assigned(TempBEncoded) then Result := TempBEncoded.IntegerData; except + Result := 0; end; end; diff --git a/source/code/main.lfm b/source/code/main.lfm index b8b9bd1..3837612 100644 --- a/source/code/main.lfm +++ b/source/code/main.lfm @@ -5,7 +5,7 @@ object FormTrackerModify: TFormTrackerModify Width = 1179 AllowDropFiles = True Caption = 'Bittorrent Tracker Editor' - ClientHeight = 587 + ClientHeight = 607 ClientWidth = 1179 Constraints.MinHeight = 500 Constraints.MinWidth = 700 @@ -16,10 +16,10 @@ object FormTrackerModify: TFormTrackerModify OnDropFiles = FormDropFiles OnShow = FormShow Position = poScreenCenter - LCLVersion = '2.2.6.0' + LCLVersion = '3.6.0.0' object PageControl: TPageControl Left = 0 - Height = 587 + Height = 607 Top = 0 Width = 1179 ActivePage = TabSheetTrackersList @@ -28,15 +28,15 @@ object FormTrackerModify: TFormTrackerModify TabOrder = 0 object TabSheetTrackersList: TTabSheet Caption = 'Trackers List' - ClientHeight = 561 + ClientHeight = 581 ClientWidth = 1171 object PanelTop: TPanel Left = 0 - Height = 561 + Height = 581 Top = 0 Width = 1171 Align = alClient - ClientHeight = 561 + ClientHeight = 581 ClientWidth = 1171 TabOrder = 0 object GroupBoxNewTracker: TGroupBox @@ -62,19 +62,19 @@ object FormTrackerModify: TFormTrackerModify end object GroupBoxPresentTracker: TGroupBox Left = 1 - Height = 353 + Height = 373 Top = 207 Width = 1169 Align = alClient Caption = 'Present trackers in all torrent files. Select the one that you want to keep.' - ClientHeight = 335 + ClientHeight = 355 ClientWidth = 1165 Constraints.MinHeight = 100 ParentBidiMode = False TabOrder = 1 object StringGridTrackerOnline: TStringGrid Left = 0 - Height = 335 + Height = 355 Top = 0 Width = 1165 Align = alClient @@ -100,30 +100,30 @@ object FormTrackerModify: TFormTrackerModify end object TabSheetPublicPrivateTorrent: TTabSheet Caption = 'Public/Private' - ClientHeight = 561 + ClientHeight = 581 ClientWidth = 1171 object PanelTopPublicTorrent: TPanel Left = 0 - Height = 561 + Height = 581 Top = 0 Width = 1171 Align = alClient - ClientHeight = 561 + ClientHeight = 581 ClientWidth = 1171 TabOrder = 0 object GroupBoxPublicPrivateTorrent: TGroupBox Left = 1 - Height = 559 + Height = 579 Top = 1 Width = 1169 Align = alClient Caption = 'Checked items are public torrent. WARNING: change public/private setting will change torrent Hash info.' - ClientHeight = 541 + ClientHeight = 561 ClientWidth = 1165 TabOrder = 0 object CheckListBoxPublicPrivateTorrent: TCheckListBox Left = 0 - Height = 541 + Height = 561 Top = 0 Width = 1165 Align = alClient @@ -135,15 +135,15 @@ object FormTrackerModify: TFormTrackerModify end object TabSheetTorrentData: TTabSheet Caption = 'Data/Info' - ClientHeight = 561 + ClientHeight = 581 ClientWidth = 1171 object StringGridTorrentData: TStringGrid Left = 0 - Height = 561 + Height = 581 Top = 0 Width = 1171 Align = alClient - ColCount = 11 + ColCount = 13 ColumnClickSorts = True Columns = < item @@ -156,11 +156,21 @@ object FormTrackerModify: TFormTrackerModify Title.Caption = 'Info Filename' Width = 250 end + item + ReadOnly = True + Title.Caption = 'Torrent Version' + Width = 100 + end + item + ReadOnly = True + Title.Caption = 'Padding (beb 47)' + Width = 100 + end item MaxSize = 250 ReadOnly = True Title.Caption = 'Info Hash' - Width = 280 + Width = 439 end item ReadOnly = True @@ -182,6 +192,7 @@ object FormTrackerModify: TFormTrackerModify Width = 50 end item + ReadOnly = True Title.Caption = 'Source' Width = 64 end @@ -201,8 +212,10 @@ object FormTrackerModify: TFormTrackerModify end item ReadOnly = True + SizePriority = 0 Title.Alignment = taRightJustify Title.Caption = 'IndexOrder (internal used)' + Width = 0 Visible = False end> FixedCols = 0 @@ -213,7 +226,9 @@ object FormTrackerModify: TFormTrackerModify ColWidths = ( 250 250 - 280 + 100 + 100 + 439 145 145 160 @@ -230,7 +245,7 @@ object FormTrackerModify: TFormTrackerModify end object TabSheetPrivateTrackers: TTabSheet Caption = 'Private Trackers' - ClientHeight = 561 + ClientHeight = 581 ClientWidth = 1171 object GroupBoxItemsForPrivateTrackers: TGroupBox Left = 0 @@ -244,15 +259,15 @@ object FormTrackerModify: TFormTrackerModify TabOrder = 0 object CheckBoxSkipAnnounceCheck: TCheckBox Left = 0 - Height = 19 + Height = 17 Top = 0 Width = 284 Align = alTop Caption = 'Skip Announce Check in the URL (-SAC)' - OnChange = CheckBoxSkipAnnounceCheckChange ParentShowHint = False ShowHint = True TabOrder = 0 + OnChange = CheckBoxSkipAnnounceCheckChange end object GroupBoxInfoSource: TGroupBox Left = 0 @@ -266,21 +281,21 @@ object FormTrackerModify: TFormTrackerModify TabOrder = 1 object CheckBoxRemoveAllSourceTag: TCheckBox Left = 8 - Height = 19 + Height = 17 Top = 8 - Width = 132 + Width = 130 Caption = 'Remove all source tag' - OnChange = CheckBoxRemoveAllSourceTagChange TabOrder = 0 + OnChange = CheckBoxRemoveAllSourceTagChange end object LabeledEditInfoSource: TLabeledEdit Left = 8 Height = 21 Hint = 'Keep empty if you do not want to add or change anything.' Top = 56 - Width = 221 + Width = 240 EditLabel.Height = 13 - EditLabel.Width = 221 + EditLabel.Width = 240 EditLabel.Caption = 'Add/Change source tag value: (-SOURCE)' EditLabel.ParentColor = False ParentShowHint = False diff --git a/source/code/main.pas b/source/code/main.pas index 5869dcf..60f47be 100644 --- a/source/code/main.pas +++ b/source/code/main.pas @@ -168,7 +168,7 @@ TFormTrackerModify = class(TForm) FControlerGridTorrentData: TControlerGridTorrentData; function CheckForAnnounce(const TrackerURL: utf8string): boolean; procedure AppendTrackersToMemoNewTrackers(TrackerList: TStringList); - procedure ShowUserErrorMessage(const ErrorText: string; const FormText: string = ''); + procedure ShowUserErrorMessage(ErrorText: string; const FormText: string = ''); function TrackerWithURLAndAnnounce(const TrackerURL: utf8string): boolean; procedure UpdateTorrent; procedure ShowHourGlassCursor(HourGlass: boolean); @@ -215,8 +215,10 @@ implementation ); //program name and version (http://semver.org/) - FORM_CAPTION = 'Bittorrent tracker editor (1.33.0/LCL ' + - lcl_version + '/FPC ' + {$I %FPCVERSION%} + ')'; + PROGRAM_VERSION = '1.33.1'; + + FORM_CAPTION = 'Bittorrent tracker editor (' + PROGRAM_VERSION + + '/LCL ' + lcl_version + '/FPC ' + {$I %FPCVERSION%} + ')'; GROUPBOX_PRESENT_TRACKERS_CAPTION = 'Present trackers in all torrent files. Select the one that you want to keep. And added to all torrent files.'; @@ -243,6 +245,11 @@ procedure TFormTrackerModify.FormCreate(Sender: TObject); begin FFolderForTrackerListLoadAndSave := GetEnvironmentVariable('XDG_DATA_HOME'); end; + // If it is a appimage program, save it in a present folder. + if GetEnvironmentVariable('APPIMAGE') <> '' then + begin // OWD = Path to working directory at the time the AppImage is called + FFolderForTrackerListLoadAndSave := GetEnvironmentVariable('OWD'); + end; if FFolderForTrackerListLoadAndSave = '' then begin // No container detected. Save in the same place as the application file @@ -533,7 +540,7 @@ function TFormTrackerModify.CheckForAnnounce(const TrackerURL: utf8string): bool (not WebTorrentTrackerURL(TrackerURL)) and (not FDragAndDropStartUp); end; -procedure TFormTrackerModify.ShowUserErrorMessage(const ErrorText: string; +procedure TFormTrackerModify.ShowUserErrorMessage(ErrorText: string; const FormText: string); begin if FConsoleMode then @@ -545,10 +552,9 @@ procedure TFormTrackerModify.ShowUserErrorMessage(const ErrorText: string; end else begin - if FormText = '' then - Application.MessageBox(PChar(@ErrorText[1]), '', MB_ICONERROR) - else - Application.MessageBox(PChar(@ErrorText[1]), PChar(@FormText[1]), MB_ICONERROR); + if FormText <> '' then + ErrorText := FormText + sLineBreak + ErrorText; + Application.MessageBox(PChar(@ErrorText[1]), '', MB_ICONERROR); end; end; @@ -616,7 +622,6 @@ procedure TFormTrackerModify.UpdateTorrent; Reply, BoxStyle, i, CountTrackers: integer; PopUpMenuStr: string; SomeFilesCannotBeWriten, SomeFilesAreReadOnly, AllFilesAreReadBackCorrectly: boolean; - begin //Update all the torrent files. @@ -634,8 +639,8 @@ procedure TFormTrackerModify.UpdateTorrent; begin //Warn user before updating the torrent BoxStyle := MB_ICONWARNING + MB_OKCANCEL; - Reply := Application.MessageBox('Warning: There is no undo.', - 'Torrent files will be change!', BoxStyle); + Reply := Application.MessageBox('Torrent files will be change!' + + sLineBreak + 'Warning: There is no undo.', '', BoxStyle); if Reply <> idOk then begin ShowHourGlassCursor(True); @@ -674,9 +679,9 @@ procedure TFormTrackerModify.UpdateTorrent; if not FConsoleMode and (CountTrackers = 0) then begin //Torrent without a tracker is posible. But is this what the user realy want? a DHT torrent. BoxStyle := MB_ICONWARNING + MB_OKCANCEL; - Reply := Application.MessageBox( - 'Warning: Create torrent file without any URL of the tracker?', - 'There are no Trackers selected!', BoxStyle); + Reply := Application.MessageBox('There are no Trackers selected!' + + sLineBreak + 'Warning: Create torrent file without any URL of the tracker?', + '', BoxStyle); if Reply <> idOk then begin ShowHourGlassCursor(False); @@ -1312,9 +1317,9 @@ procedure TFormTrackerModify.MenuTrackersAllTorrentArePublicPrivateClick( i: integer; begin //Warn user about torrent Hash. - if Application.MessageBox( + if Application.MessageBox('Are you sure!' + sLineBreak + 'Warning: Changing the public/private torrent flag will change the info hash.', - 'Are you sure!', MB_ICONWARNING + MB_OKCANCEL) <> idOk then + '', MB_ICONWARNING + MB_OKCANCEL) <> idOk then exit; //Set all the trackers publick/private CheckBoxRemoveAllSourceTag ON or OFF @@ -1417,7 +1422,6 @@ procedure TFormTrackerModify. end; procedure TFormTrackerModify.MenuUpdateTorrentAddBeforeRemoveNewClick(Sender: TObject); - begin //User have selected to add new tracker. FTrackerList.TrackerListOrderForUpdatedTorrent := @@ -1523,6 +1527,7 @@ procedure TFormTrackerModify.FormDropFiles(Sender: TObject; MemoNewTrackers.Append(UTF8Trim(TrackerFileNameStringList.Text)); except //supress any error in loading the file + FileNameOrDirStr := FileNameOrDirStr; end; end; @@ -1743,7 +1748,25 @@ procedure TFormTrackerModify.ViewUpdateOneTorrentFileDecoded; //Copy all the torrent info to the grid column. FControlerGridTorrentData.TorrentFile := TorrentFileNameStr; FControlerGridTorrentData.InfoFileName := FDecodePresentTorrent.Name; - FControlerGridTorrentData.InfoHash := FDecodePresentTorrent.InfoHash; + FControlerGridTorrentData.TorrentVersion := + FDecodePresentTorrent.TorrentVersionToString; + case FDecodePresentTorrent.TorrentVersion of + tv_V1: + begin + FControlerGridTorrentData.InfoHash := 'V1: ' + FDecodePresentTorrent.InfoHash_V1; + end; + tv_V2: + begin + FControlerGridTorrentData.InfoHash := 'V2: ' + FDecodePresentTorrent.InfoHash_V2; + end; + tv_Hybrid: + begin // Show only V2 hash. No space for both V1 and V2 + FControlerGridTorrentData.InfoHash := 'V2: ' + FDecodePresentTorrent.InfoHash_V2; + end; + else + FControlerGridTorrentData.InfoHash := 'N/A' + end; + FControlerGridTorrentData.Padding := FDecodePresentTorrent.PaddingToString; FControlerGridTorrentData.CreatedOn := DateTimeStr; FControlerGridTorrentData.CreatedBy := FDecodePresentTorrent.CreatedBy; FControlerGridTorrentData.Comment := FDecodePresentTorrent.Comment; diff --git a/source/code/ngosang_trackerslist.pas b/source/code/ngosang_trackerslist.pas index e900ba3..2bfebec 100644 --- a/source/code/ngosang_trackerslist.pas +++ b/source/code/ngosang_trackerslist.pas @@ -95,6 +95,7 @@ function TngosangTrackerList.DownloadTracker(ngosang_List: Tngosang_List): TStri except //No OpenSSL or web server is down + FTRackerList[ngosang_List].Clear; end; Result := FTrackerList[ngosang_List]; diff --git a/source/code/torrent_miscellaneous.pas b/source/code/torrent_miscellaneous.pas index a2abc47..525dbe1 100644 --- a/source/code/torrent_miscellaneous.pas +++ b/source/code/torrent_miscellaneous.pas @@ -246,8 +246,11 @@ function ByteSizeToBiggerSizeFormatStr(ByteSize: int64): string; Result := Format('%0.2f MiB', [ByteSize / (1024 * 1024)]) else if ByteSize >= (1024) then - Result := Format('%0.2f KiB', [ByteSize / 1024]); - Result := Result + Format(' (%d Bytes)', [ByteSize]); + Result := Format('%0.2f KiB', [ByteSize / 1024]) + else + Result := ''; + + Result := Result + Format(' (%d Bytes)', [ByteSize]); end; @@ -629,11 +632,11 @@ function ConsoleModeDecodeParameter(out FileNameOrDirStr: UTF8String; -U3 "path_to_folder" -SAC -SOURCE "ABC" } + Result := False; case Paramcount of 0: begin TrackerList.LogStringList.Add('ERROR: There are no parameter detected.'); - Result := False; exit; end; 1: diff --git a/source/code/trackerlist_online.pas b/source/code/trackerlist_online.pas index 5eb00ac..65c09d7 100644 --- a/source/code/trackerlist_online.pas +++ b/source/code/trackerlist_online.pas @@ -90,7 +90,10 @@ function TTrackerListOnline.TrackerListOnlineStatusToString( tos_dead: Result := 'Dead'; tos_unknown: Result := 'Unknown'; else - assert(True, 'Unknown TTrackerListOnlineStatus') + begin + Result := ''; + assert(True, 'Unknown TTrackerListOnlineStatus') + end; end; end; diff --git a/source/project/tracker_editor/trackereditor.lpi b/source/project/tracker_editor/trackereditor.lpi index bc0e2ec..9b49c62 100644 --- a/source/project/tracker_editor/trackereditor.lpi +++ b/source/project/tracker_editor/trackereditor.lpi @@ -28,7 +28,7 @@ - + @@ -69,9 +69,20 @@ - + + @@ -106,7 +117,7 @@ - + @@ -156,6 +167,21 @@ + + + + + + + + + + + + + + + @@ -166,7 +192,7 @@ - + diff --git a/source/project/tracker_editor/trackereditor.lpr b/source/project/tracker_editor/trackereditor.lpr index 808047a..4b3a3e1 100644 --- a/source/project/tracker_editor/trackereditor.lpr +++ b/source/project/tracker_editor/trackereditor.lpr @@ -7,9 +7,9 @@ cthreads, {$ENDIF}{$ENDIF} Interfaces, // this includes the LCL widgetset - Forms, main, bencode, decodetorrent, controlergridtorrentdata, -controler_trackerlist_online, trackerlist_online, -controler_treeview_torrent_data; + Forms, DCPsha256, DCPconst, DCPcrypt2, main, bencode, decodetorrent, + controlergridtorrentdata, controler_trackerlist_online, trackerlist_online, + controler_treeview_torrent_data; {$R *.res} diff --git a/source/test/test_start_up_parameter.pas b/source/test/test_start_up_parameter.pas index 60f2807..5285a7e 100644 --- a/source/test/test_start_up_parameter.pas +++ b/source/test/test_start_up_parameter.pas @@ -104,8 +104,8 @@ implementation TORRENT_FOLDER = 'test_torrent'; END_USER_FOLDER = 'enduser'; - //there are 3 test torrent files in 'test_torrent' folder. - TEST_TORRENT_FILES_COUNT = 3; + //there are 5 test torrent files in 'test_torrent' folder. + TEST_TORRENT_FILES_COUNT = 5; procedure TTestStartUpParameter.Test_Paramater_U0; begin diff --git a/submodule/dcpcrypt b/submodule/dcpcrypt new file mode 160000 index 0000000..14586ed --- /dev/null +++ b/submodule/dcpcrypt @@ -0,0 +1 @@ +Subproject commit 14586ed66d15fd91530ed5dfaab8a8e4bb8959ff diff --git a/test_torrent/bittorrent-v2-hybrid-test.torrent b/test_torrent/bittorrent-v2-hybrid-test.torrent new file mode 100644 index 0000000..9a5c876 Binary files /dev/null and b/test_torrent/bittorrent-v2-hybrid-test.torrent differ diff --git a/test_torrent/bittorrent-v2-test.torrent b/test_torrent/bittorrent-v2-test.torrent new file mode 100644 index 0000000..8ad4c7e Binary files /dev/null and b/test_torrent/bittorrent-v2-test.torrent differ