diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml new file mode 100644 index 0000000..116bac6 --- /dev/null +++ b/.github/workflows/ci_cd.yml @@ -0,0 +1,73 @@ +# .github/workflows/ci.yml +name: CI/CD Pipeline + +on: + workflow_dispatch: + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + python-version: [3.13] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Poetry + uses: snok/install-poetry@v1 + + - name: Install dependencies + run: poetry install + + - name: Run tests + run: poetry run pytest + + - name: Run linting + run: | + poetry run flake8 src/ + poetry run black --check src/ + poetry run mypy src/ + + build: + needs: test + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.13 + + - name: Install Poetry + uses: snok/install-poetry@v1 + + - name: Generate lock file + run: poetry lock + + - name: Install dependencies + run: poetry install + + - name: Build application + run: | + # Set environment variable for CI mode + export CI_MODE=true + poetry run python src/main.py + env: + CI_MODE: "true" + + - name: Build complete + run: echo "Build process completed successfully" \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..cc71125 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,126 @@ +# .github/workflows/release.yml +name: Release + +on: + workflow_dispatch: + +jobs: + release: + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + asset_name: android-file-handler-linux + - os: windows-latest + asset_name: android-file-handler-windows.exe + + steps: + - uses: actions/checkout@v4 + + - name: Install AppImage dependencies (Linux) + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt update + sudo apt install -y fuse libfuse2 curl imagemagick + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.13' + + - name: Install Poetry + uses: snok/install-poetry@v1 + with: + version: latest + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Cache build tools + uses: actions/cache@v3 + with: + path: | + python3.13.0-cp313-cp313-linux_x86_64.AppImage + appimagetool + key: build-tools-${{ matrix.os }} + + - name: Download python-appimage (Linux) + if: matrix.os == 'ubuntu-latest' + run: | + curl -L -o python3.13.0-cp313-cp313-linux_x86_64.AppImage https://github.com/niess/python-appimage/releases/download/python3.13/python3.13.0-cp313-cp313-linux_x86_64.AppImage + chmod +x python3.13.0-cp313-cp313-linux_x86_64.AppImage + + - name: Build AppImage (Linux) + if: matrix.os == 'ubuntu-latest' + run: | + # 1. Extract Python AppImage first + ./python3.13.0-cp313-cp313-linux_x86_64.AppImage --appimage-extract + + # 2. Create AppDir structure + mkdir -p AppDir/usr/bin AppDir/usr/share/{applications,icons/hicolor/256x256/apps} + mkdir -p AppDir/usr/lib/python3.13/site-packages + + # 3. Copy Python runtime to AppDir first + cp -r squashfs-root/usr/* AppDir/usr/ + + # 4. Install dependencies using AppImage Python + export PATH="$PWD/squashfs-root/usr/bin:$PATH" + ./squashfs-root/usr/bin/python3 -m pip install poetry + poetry install --no-dev + + # 5. Copy application source + cp -r src/ AppDir/usr/bin/ + + # 6. Copy installed packages to AppDir + cp -r .venv/lib/python3.13/site-packages/* AppDir/usr/lib/python3.13/site-packages/ 2>/dev/null || true + + # 7. Create AppRun script (after Python is available) + cat > AppDir/AppRun << 'EOF' + #!/bin/bash + HERE="$(dirname "$(readlink -f "${0}")")" + export APPDIR="$HERE" + export PYTHONPATH="$HERE/usr/lib/python3.13/site-packages:$PYTHONPATH" + exec "$HERE/usr/bin/python3" "$HERE/usr/bin/src/main.py" "$@" + EOF + chmod +x AppDir/AppRun + + # 8. Create .desktop file + cat > AppDir/android-file-handler.desktop << 'EOF' + [Desktop Entry] + Type=Application + Name=Android File Handler + Exec=AppRun + Icon=android-file-handler + Categories=Utility; + EOF + + # 9. Create icon + convert -size 256x256 xc:lightblue -pointsize 48 -fill black -gravity center -annotate +0+0 "AFH" AppDir/usr/share/icons/hicolor/256x256/apps/android-file-handler.png || echo "Icon creation skipped" + + # 10. Download and setup appimagetool + for attempt in 1 2 3; do + curl -L -o appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage && break + echo "Download attempt $attempt failed, retrying..." + sleep 5 + done + chmod +x appimagetool + + # 11. Create AppImage directly in dist folder + mkdir -p dist + if ! ./appimagetool AppDir dist/android-file-handler-linux; then + echo "AppImage creation failed" + exit 1 + fi + + - name: Build executable (Windows) + if: matrix.os == 'windows-latest' + run: | + poetry lock + poetry install + poetry run pyinstaller --onefile src/main.py --distpath dist --name android-file-handler-windows + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: dist/${{ matrix.asset_name }} + generate_release_notes: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 52c7f9b..83de921 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -src/platform-tools +platform-tools** +dist/ # Python cache files __pycache__/ diff --git a/README.md b/README.md index d31aa2b..2ae387a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Android File Handler (ADB) -A simple Windows GUI tool to pull folders from an Android device using ADB (Android Debug Bridge). The tool automatically downloads the latest ADB platform-tools if not present, checks for a connected device, and provides a user-friendly interface for selecting remote and local folders. +A simple Windows GUI tool to pull folders from an Android device using ADB (Android Debug Bridge). The tool automatically downloads the latest ADB platform-tools if not present, checks for a connected device, and provides a user-friendly interface for selecting remote and local single files and folders. ## Features @@ -16,13 +16,14 @@ A simple Windows GUI tool to pull folders from an Android device using ADB (Andr - Windows or Linux - Python 3.8+ - [Poetry](https://python-poetry.org/) for dependency management +- (Optional) pyenv for Python version managment ## Installation 1. Clone this repository: ```sh - git clone https://github.com/JMR-dev/android_file_handler_adb.git + git clone git@github.com:JMR-dev/android_file_handler_adb.git cd android_file_handler_adb ``` @@ -37,15 +38,13 @@ A simple Windows GUI tool to pull folders from an Android device using ADB (Andr 1. Activate the virtual environment: ```sh - poetry shell + poetry env activate ``` - Or manually activate the environment as described in the Poetry documentation. - 2. Run the application: ```sh - python android_folder_puller.py + poetry run python3 src/main.py ``` 3. Follow the on-screen instructions: @@ -57,7 +56,6 @@ A simple Windows GUI tool to pull folders from an Android device using ADB (Andr - **ADB not found:** The tool will attempt to download and extract ADB automatically. - **No device detected:** Ensure your device is connected via USB and USB debugging is enabled. The app provides instructions if needed. -- **Permissions:** Some folders on the device may require root access. ## License diff --git a/pyproject.toml b/pyproject.toml index 62b68cc..662caa6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,11 +29,19 @@ test-adb = "python -c 'from src.adb_manager import ADBManager; m = ADBManager(); clean-adb = "python -c 'import shutil, os; shutil.rmtree(\"src/platform-tools\", ignore_errors=True); print(\"ADB cache cleared\")'" [tool.poetry.group.dev.dependencies] -black = "^25.1.0" +black = "^25.0.0" +flake8 = "^6.0.0" mypy = "^1.5.0" -pylint = "^3.3.8" -pytest = "^8.4.1" -pyinstaller = "^6.15.0" +pre-commit = "^3.4.0" + +[tool.poetry.group.test.dependencies] +pytest = "^7.4.0" +pytest-qt = "^4.2.0" +pytest-mock = "^3.11.0" +pytest-cov = "^4.1.0" + +[tool.poetry.group.build.dependencies] +pyinstaller = "^5.13.0" [tool.black] line-length = 88 diff --git a/scripts/build_AppImage.sh b/scripts/build_AppImage.sh new file mode 100644 index 0000000..8c04fcb --- /dev/null +++ b/scripts/build_AppImage.sh @@ -0,0 +1,78 @@ +# Check if required packages are installed +missing_packages=() +for package in fuse libfuse2 curl imagemagick; do + if ! dpkg -l | grep -q "^ii $package "; then + missing_packages+=("$package") + fi +done + +# Only run apt commands if there are missing packages +if [ ${#missing_packages[@]} -gt 0 ]; then + echo "Installing missing packages: ${missing_packages[*]}" + sudo apt update + sudo apt install -y "${missing_packages[@]}" +else + echo "All required packages are already installed" +fi + +curl -L -o python3.13.0-cp313-cp313-linux_x86_64.AppImage https://github.com/niess/python-appimage/releases/download/python3.13/python3.13.0-cp313-cp313-linux_x86_64.AppImage +chmod +x python3.13.0-cp313-cp313-linux_x86_64.AppImage + +# 1. Extract Python AppImage first +./python3.13.0-cp313-cp313-linux_x86_64.AppImage --appimage-extract + +# 2. Create AppDir structure +mkdir -p AppDir/usr/bin AppDir/usr/share/{applications,icons/hicolor/256x256/apps} +mkdir -p AppDir/usr/lib/python3.13/site-packages + +# 3. Copy Python runtime to AppDir first +cp -r squashfs-root/usr/* AppDir/usr/ + +# 4. Install dependencies using AppImage Python +export PATH="$PWD/squashfs-root/usr/bin:$PATH" +./squashfs-root/usr/bin/python3 -m pip install poetry +poetry install --no-dev + +# 5. Copy application source +cp -r src/ AppDir/usr/bin/ + +# 6. Copy installed packages to AppDir +cp -r .venv/lib/python3.13/site-packages/* AppDir/usr/lib/python3.13/site-packages/ 2>/dev/null || true + +# 7. Create AppRun script (after Python is available) +cat > AppDir/AppRun << 'EOF' +#!/bin/bash +HERE="$(dirname "$(readlink -f "${0}")")" +export APPDIR="$HERE" +export PYTHONPATH="$HERE/usr/lib/python3.13/site-packages:$PYTHONPATH" +exec "$HERE/usr/bin/python3" "$HERE/usr/bin/src/main.py" "$@" +EOF +chmod +x AppDir/AppRun + +# 8. Create .desktop file +cat > AppDir/android-file-handler.desktop << 'EOF' +[Desktop Entry] +Type=Application +Name=Android File Handler +Exec=AppRun +Icon=android-file-handler +Categories=Utility; +EOF + +# 9. Create icon +convert -size 256x256 xc:lightblue -pointsize 48 -fill black -gravity center -annotate +0+0 "AFH" AppDir/usr/share/icons/hicolor/256x256/apps/android-file-handler.png || echo "Icon creation skipped" + +# 10. Download and setup appimagetool +for attempt in 1 2 3; do + curl -L -o appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage && break + echo "Download attempt $attempt failed, retrying..." + sleep 5 +done +chmod +x appimagetool + +# 11. Create AppImage directly in dist folder +mkdir -p dist +if ! ./appimagetool AppDir dist/android-file-handler-linux; then + echo "AppImage creation failed" + exit 1 +fi \ No newline at end of file diff --git a/scripts/build_exe.sh b/scripts/build_exe.sh new file mode 100644 index 0000000..3575b0e --- /dev/null +++ b/scripts/build_exe.sh @@ -0,0 +1,3 @@ +poetry lock +poetry install +poetry run pyinstaller --onefile src/main.py --distpath dist --name android-file-handler-windows \ No newline at end of file diff --git a/src/gui/main_window.py b/src/gui/main_window.py index 608e944..58ebf26 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -6,6 +6,7 @@ Core GUI application window for Android file transfers. import os import sys import threading +import time import tkinter as tk from tkinter import messagebox, filedialog, ttk @@ -807,6 +808,14 @@ def main(): sys.exit(1) app = AndroidFileHandlerGUI() + # If running in CI mode, auto-close after 10 seconds + if os.getenv('CI_MODE') == 'true': + def auto_close(): + time.sleep(10) + app.quit() + + threading.Thread(target=auto_close, daemon=True).start() + print("CI Mode: Application will auto-close in 10 seconds") app.mainloop()