Updated user instructions and added license agreement
This commit is contained in:
@@ -0,0 +1 @@
|
||||
1
|
||||
Generated
+2
-120
@@ -50,8 +50,6 @@ mypy-extensions = ">=0.4.3"
|
||||
packaging = ">=22.0"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
@@ -298,9 +296,6 @@ files = [
|
||||
{file = "coverage-7.10.4.tar.gz", hash = "sha256:25f5130af6c8e7297fd14634955ba9e1697f47143f289e2a23284177c0061d27"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
|
||||
|
||||
[package.extras]
|
||||
toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
|
||||
|
||||
@@ -316,25 +311,6 @@ files = [
|
||||
{file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.3.0"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["test"]
|
||||
markers = "python_version < \"3.11\""
|
||||
files = [
|
||||
{file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"},
|
||||
{file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""}
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.19.1"
|
||||
@@ -394,31 +370,6 @@ files = [
|
||||
[package.extras]
|
||||
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "8.7.0"
|
||||
description = "Read metadata from Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["build"]
|
||||
markers = "python_version == \"3.9\""
|
||||
files = [
|
||||
{file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"},
|
||||
{file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
zipp = ">=3.20"
|
||||
|
||||
[package.extras]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
|
||||
cover = ["pytest-cov"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
enabler = ["pytest-enabler (>=2.2)"]
|
||||
perf = ["ipython"]
|
||||
test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
|
||||
type = ["pytest-mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.1.0"
|
||||
@@ -510,7 +461,6 @@ files = [
|
||||
[package.dependencies]
|
||||
mypy_extensions = ">=1.0.0"
|
||||
pathspec = ">=0.9.0"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing_extensions = ">=4.6.0"
|
||||
|
||||
[package.extras]
|
||||
@@ -704,7 +654,6 @@ files = [
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
importlib_metadata = {version = ">=4.6", markers = "python_version < \"3.10\""}
|
||||
packaging = ">=22.0"
|
||||
setuptools = ">=42.0.0"
|
||||
|
||||
@@ -722,11 +671,9 @@ files = [
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
@@ -908,49 +855,6 @@ enabler = ["pytest-enabler (>=2.2)"]
|
||||
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
|
||||
type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.2.1"
|
||||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev", "test"]
|
||||
files = [
|
||||
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
|
||||
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
|
||||
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
|
||||
]
|
||||
markers = {dev = "python_version < \"3.11\"", test = "python_full_version <= \"3.11.0a6\""}
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.14.1"
|
||||
@@ -997,34 +901,12 @@ files = [
|
||||
distlib = ">=0.3.7,<1"
|
||||
filelock = ">=3.12.2,<4"
|
||||
platformdirs = ">=3.9.1,<5"
|
||||
typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
|
||||
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.23.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["build"]
|
||||
markers = "python_version == \"3.9\""
|
||||
files = [
|
||||
{file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"},
|
||||
{file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
|
||||
cover = ["pytest-cov"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
enabler = ["pytest-enabler (>=2.2)"]
|
||||
test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
|
||||
type = ["pytest-mypy"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = "<3.13,>=3.9"
|
||||
content-hash = "224bda1d49801be6e0e993ae685177d1b6cce307395933b8ff1f25a6b378dab4"
|
||||
python-versions = "<3.13,>=3.12"
|
||||
content-hash = "dfb6152cb0fb34dcda59387fbaef4f445b0f38437bd7bdfe25b38b62b14d527e"
|
||||
|
||||
+2
-7
@@ -7,11 +7,11 @@ authors = [
|
||||
]
|
||||
license = { text = "MIT" }
|
||||
readme = "README.md"
|
||||
requires-python = "<3.13,>=3.9"
|
||||
requires-python = "<3.13,>=3.12"
|
||||
dependencies = ["requests>=2.32.4,<3.0.0"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "<3.13,>=3.9"
|
||||
python = "<3.13,>=3.12"
|
||||
requests = ">=2.32.4,<3.0.0"
|
||||
|
||||
[build-system]
|
||||
@@ -22,11 +22,6 @@ build-backend = "poetry.core.masonry.api"
|
||||
# Main application entry point
|
||||
android-file-handler = "src.main:main"
|
||||
|
||||
# Development and maintenance scripts
|
||||
dev = "python src/main.py"
|
||||
gui = "python src/gui.py"
|
||||
test-adb = "python -c 'from src.adb_manager import ADBManager; m = ADBManager(); print(f\"ADB Available: {m.check_device()}\")"
|
||||
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.0.0"
|
||||
|
||||
+54
-4
@@ -15,6 +15,52 @@ import time
|
||||
import re
|
||||
from typing import Optional, Tuple, Callable
|
||||
|
||||
def get_executable_directory() -> str:
|
||||
"""Get the directory containing the executable or script."""
|
||||
if getattr(sys, 'frozen', False):
|
||||
# Running as executable (PyInstaller, cx_Freeze, etc.)
|
||||
return os.path.dirname(sys.executable)
|
||||
else:
|
||||
# Running as script - use the script's directory
|
||||
return os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
def get_platform_tools_directory() -> str:
|
||||
"""Get platform-tools directory with executable and AppImage support."""
|
||||
base_dir = get_executable_directory()
|
||||
|
||||
# For AppImages running from temporary mounts, use persistent storage
|
||||
if sys.platform.startswith('linux') and ('tmp' in base_dir and '.mount_' in base_dir):
|
||||
# AppImage temporary mount - use persistent user directory
|
||||
home_dir = os.path.expanduser("~")
|
||||
persistent_dir = os.path.join(home_dir, ".android-file-handler", "platform-tools")
|
||||
os.makedirs(os.path.dirname(persistent_dir), exist_ok=True)
|
||||
return persistent_dir
|
||||
|
||||
# Check if we're in development mode (running from src/ directory)
|
||||
if not getattr(sys, 'frozen', False):
|
||||
# Running as script - check if we're in src/ directory or subdirectory
|
||||
if base_dir.endswith('src'):
|
||||
# Already in src directory - place platform-tools here
|
||||
return os.path.join(base_dir, "platform-tools")
|
||||
elif base_dir.endswith('gui') or os.path.basename(base_dir) in ['gui']:
|
||||
# In src/gui subdirectory - go up one level to src
|
||||
src_dir = os.path.dirname(base_dir)
|
||||
return os.path.join(src_dir, "platform-tools")
|
||||
else:
|
||||
# Not in src structure - assume we need to find/create src directory
|
||||
# This handles cases where the script might be run from project root
|
||||
current_dir = base_dir
|
||||
src_dir = os.path.join(current_dir, "src")
|
||||
if os.path.exists(src_dir):
|
||||
return os.path.join(src_dir, "platform-tools")
|
||||
else:
|
||||
# Fallback to current directory
|
||||
return os.path.join(base_dir, "src", "platform-tools")
|
||||
|
||||
# Running as executable - use directory next to binary
|
||||
return os.path.join(base_dir, "platform-tools")
|
||||
|
||||
|
||||
# Constants
|
||||
ADB_WIN_ZIP_URL = (
|
||||
"https://dl.google.com/android/repository/platform-tools-latest-windows.zip"
|
||||
@@ -22,9 +68,7 @@ ADB_WIN_ZIP_URL = (
|
||||
ADB_LINUX_ZIP_URL = (
|
||||
"https://dl.google.com/android/repository/platform-tools-latest-linux.zip"
|
||||
)
|
||||
LOCAL_ADB_FOLDER = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "platform-tools"
|
||||
)
|
||||
LOCAL_ADB_FOLDER = get_platform_tools_directory()
|
||||
OS_TYPE = sys.platform
|
||||
|
||||
# Determine ADB binary name based on platform
|
||||
@@ -100,7 +144,11 @@ class ADBManager:
|
||||
response.raise_for_status()
|
||||
|
||||
binary_archive = zipfile.ZipFile(io.BytesIO(response.content))
|
||||
binary_archive.extractall(LOCAL_ADB_FOLDER)
|
||||
|
||||
# Extract to parent directory since the zip contains platform-tools/ folder
|
||||
extract_to = os.path.dirname(LOCAL_ADB_FOLDER)
|
||||
os.makedirs(extract_to, exist_ok=True)
|
||||
binary_archive.extractall(extract_to)
|
||||
|
||||
if OS_TYPE.startswith("linux"):
|
||||
if os.path.exists(ADB_BINARY_PATH):
|
||||
@@ -781,3 +829,5 @@ def is_adb_available() -> bool:
|
||||
def get_platform_type() -> str:
|
||||
"""Get the current platform type."""
|
||||
return OS_TYPE
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
"""
|
||||
License Agreement Module
|
||||
Handles MIT license agreement functionality.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox, scrolledtext
|
||||
|
||||
|
||||
def get_license_file_path():
|
||||
"""Get the path to the license agreement file."""
|
||||
# Get the directory where the application is running from
|
||||
if getattr(sys, 'frozen', False):
|
||||
# Running as executable
|
||||
app_dir = os.path.dirname(sys.executable)
|
||||
else:
|
||||
# Running as script - use directory containing the main script
|
||||
app_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
return os.path.join(app_dir, "license_agreed.ini")
|
||||
|
||||
|
||||
def check_license_agreement():
|
||||
"""Check if user has already agreed to the license."""
|
||||
license_file = get_license_file_path()
|
||||
try:
|
||||
if os.path.exists(license_file):
|
||||
with open(license_file, 'r') as f:
|
||||
content = f.read().strip()
|
||||
return content == "1"
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def save_license_agreement():
|
||||
"""Save that the user has agreed to the license."""
|
||||
license_file = get_license_file_path()
|
||||
try:
|
||||
# Create directory if it doesn't exist
|
||||
os.makedirs(os.path.dirname(license_file), exist_ok=True)
|
||||
with open(license_file, 'w') as f:
|
||||
f.write("1")
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def get_mit_license_text():
|
||||
"""Get the MIT license text."""
|
||||
return """MIT License
|
||||
|
||||
Copyright (c) 2025 Jason Ross
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE."""
|
||||
|
||||
|
||||
class LicenseAgreementFrame(tk.Frame):
|
||||
"""License agreement UI frame that can be embedded in the main window."""
|
||||
|
||||
def __init__(self, parent, on_agree_callback):
|
||||
"""Initialize the license agreement frame.
|
||||
|
||||
Args:
|
||||
parent: Parent widget
|
||||
on_agree_callback: Function to call when user agrees to license
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.on_agree_callback = on_agree_callback
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
"""Setup the license agreement UI."""
|
||||
# Configure the frame to fill the window
|
||||
self.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)
|
||||
|
||||
# Header
|
||||
header_label = tk.Label(
|
||||
self,
|
||||
text="License Agreement",
|
||||
font=("Arial", 16, "bold")
|
||||
)
|
||||
header_label.pack(pady=(0, 10))
|
||||
|
||||
# Instruction
|
||||
instruction_label = tk.Label(
|
||||
self,
|
||||
text="Please read and accept the license agreement to continue using Android File Handler:",
|
||||
font=("Arial", 10),
|
||||
wraplength=500
|
||||
)
|
||||
instruction_label.pack(pady=(0, 15))
|
||||
|
||||
# License text area
|
||||
text_frame = tk.Frame(self)
|
||||
text_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 20))
|
||||
|
||||
self.license_text = scrolledtext.ScrolledText(
|
||||
text_frame,
|
||||
wrap=tk.WORD,
|
||||
width=80,
|
||||
height=20,
|
||||
font=("Courier", 9),
|
||||
state=tk.DISABLED,
|
||||
bg="#f8f8f8",
|
||||
relief=tk.SUNKEN,
|
||||
bd=2
|
||||
)
|
||||
self.license_text.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Insert license text
|
||||
self.license_text.config(state=tk.NORMAL)
|
||||
self.license_text.insert(tk.END, get_mit_license_text())
|
||||
self.license_text.config(state=tk.DISABLED)
|
||||
|
||||
# Button frame
|
||||
button_frame = tk.Frame(self)
|
||||
button_frame.pack(fill=tk.X, pady=(10, 0))
|
||||
|
||||
# Buttons
|
||||
self.disagree_btn = tk.Button(
|
||||
button_frame,
|
||||
text="Disagree & Exit",
|
||||
command=self.on_disagree,
|
||||
bg="#ff6b6b",
|
||||
fg="white",
|
||||
font=("Arial", 11, "bold"),
|
||||
width=15,
|
||||
height=2
|
||||
)
|
||||
self.disagree_btn.pack(side=tk.LEFT)
|
||||
|
||||
self.agree_btn = tk.Button(
|
||||
button_frame,
|
||||
text="I Agree",
|
||||
command=self.on_agree,
|
||||
bg="#4CAF50",
|
||||
fg="white",
|
||||
font=("Arial", 11, "bold"),
|
||||
width=15,
|
||||
height=2
|
||||
)
|
||||
self.agree_btn.pack(side=tk.RIGHT)
|
||||
|
||||
# Center text
|
||||
center_label = tk.Label(
|
||||
button_frame,
|
||||
text="You must agree to the license terms to use this software",
|
||||
font=("Arial", 9),
|
||||
fg="#666666"
|
||||
)
|
||||
center_label.pack(expand=True)
|
||||
|
||||
def on_agree(self):
|
||||
"""Handle user clicking Agree."""
|
||||
if save_license_agreement():
|
||||
self.on_agree_callback()
|
||||
else:
|
||||
messagebox.showerror(
|
||||
"Error",
|
||||
"Could not save license agreement. Please check file permissions and try again."
|
||||
)
|
||||
|
||||
def on_disagree(self):
|
||||
"""Handle user clicking Disagree."""
|
||||
# Ask for confirmation
|
||||
result = messagebox.askyesno(
|
||||
"Exit Application",
|
||||
"Are you sure you want to exit? You must agree to the license terms to use this software.",
|
||||
icon="warning"
|
||||
)
|
||||
if result:
|
||||
sys.exit(0)
|
||||
+117
-25
@@ -8,7 +8,7 @@ import sys
|
||||
import threading
|
||||
import time
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox, filedialog, ttk
|
||||
from tkinter import messagebox, filedialog, ttk, scrolledtext
|
||||
|
||||
try:
|
||||
# Try relative import first (when used as module)
|
||||
@@ -26,52 +26,122 @@ except ImportError:
|
||||
try:
|
||||
# Try relative imports first
|
||||
from .file_browser import AndroidFileBrowser
|
||||
from .license_agreement import LicenseAgreementFrame, check_license_agreement
|
||||
except ImportError:
|
||||
# Fall back to direct imports
|
||||
from src.gui.file_browser import AndroidFileBrowser
|
||||
from src.gui.license_agreement import LicenseAgreementFrame, check_license_agreement
|
||||
|
||||
|
||||
class AndroidFileHandlerGUI(tk.Tk):
|
||||
"""Main GUI application for Android file transfers."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the main GUI application."""
|
||||
super().__init__()
|
||||
|
||||
|
||||
# Initialize business logic
|
||||
self.adb_manager = ADBManager()
|
||||
|
||||
|
||||
# Transfer tracking for thread safety
|
||||
self.current_transfer_id = 0
|
||||
self.device_connected = False # Track device connection state
|
||||
self.device_connected = False
|
||||
|
||||
# License agreement tracking
|
||||
self.license_agreed = check_license_agreement()
|
||||
self.license_frame = None
|
||||
self.main_ui_created = False
|
||||
|
||||
self.troubleshooting_steps = (
|
||||
"Android device appears to have been disconnected and/or USB debugging is disabled.\n"
|
||||
"Please ensure your Android device is securely connected to the computer at both ends.\n\n"
|
||||
"Please ensure your Android device is securely connected at both ends.\n\n"
|
||||
|
||||
"To enable USB debugging:\n"
|
||||
"1. Connect your device to the computer via USB.\n"
|
||||
"2. Open Settings → About phone.\n"
|
||||
"3. Tap 'Build number' seven times to unlock Developer Options.\n"
|
||||
"4. Go back to Settings → Developer Options.\n"
|
||||
"5. Enable 'USB debugging'.\n"
|
||||
"6. Connect your phone via USB and accept the prompt to allow debugging.\n\n"
|
||||
"Additionally, ensure your device is set to 'File Transfer' mode:\n"
|
||||
"1. When you connect your device, swipe down to check the notification panel.\n"
|
||||
"2. Look for a 'USB' notification that will tell you what mode the device is in (usually 'Charging over USB') and tap it.\n"
|
||||
"3. Select 'File Transfer' or 'MTP' mode.\n\n"
|
||||
"After enabling USB debugging and file transfer mode, please click the 'Recheck for connected Android device' button to try again."
|
||||
"1. Connect your device to the computer via USB\n"
|
||||
"2. Open Settings → About phone\n"
|
||||
"3. Tap 'Build number' seven times to unlock Developer Options\n"
|
||||
" (You only need to do this once unless you disable it, reset settings, or wipe your device)\n"
|
||||
"4. Navigate back and go to System → Developer Options\n"
|
||||
"5. Find and enable 'USB debugging'\n"
|
||||
" (Tip: Use the search icon at the top if you can't find it)\n"
|
||||
"6. Connect via USB and tap 'Trust' when prompted\n"
|
||||
" (Checking 'Remember' is recommended for future transfers)\n\n"
|
||||
|
||||
"Ensure File Transfer mode is enabled:\n"
|
||||
"1. After connecting, swipe down to view notifications\n"
|
||||
"2. Look for a USB notification (often shows 'Charging over USB')\n"
|
||||
"3. Tap the notification and select 'File Transfer' or 'MTP' mode\n\n"
|
||||
|
||||
"Note: Menu names may vary by Android version:\n"
|
||||
"• Some devices show 'Developer options' under 'System'\n"
|
||||
"• Others may have it directly in the main Settings menu\n"
|
||||
"• Samsung devices might show 'Software information' instead of 'About phone'\n\n"
|
||||
|
||||
"If you're still having trouble:\n"
|
||||
"• Try a different USB cable or port (some cables only support charging)\n"
|
||||
"• Restart both your phone and computer\n"
|
||||
"• Make sure your phone screen is unlocked when connecting\n"
|
||||
"• Set your phone screen timeout to 30 minutes (especially for long transfers)\n\n"
|
||||
"• Use a different computer to test if the issue is computer-specific\n"
|
||||
|
||||
"Windows users: If you see a driver installation popup, please allow it to complete.\n"
|
||||
"Linux users: You may need to run 'sudo usermod -a -G plugdev $USER' and reboot.\n"
|
||||
|
||||
"After completing these steps, click 'Recheck for connected Android device' to try again."
|
||||
)
|
||||
# Setup UI
|
||||
self._setup_ui()
|
||||
self._initialize_components()
|
||||
self._initialize_app()
|
||||
|
||||
# Setup UI based on license status
|
||||
self._setup_initial_ui()
|
||||
|
||||
def _setup_ui(self):
|
||||
"""Setup the user interface."""
|
||||
def _setup_initial_ui(self):
|
||||
"""Setup the initial UI - either license agreement or main interface."""
|
||||
# Window configuration
|
||||
self.title("Android File Handler")
|
||||
self.geometry("520x320")
|
||||
self.minsize(520, 320)
|
||||
self.resizable(True, True)
|
||||
|
||||
if not self.license_agreed:
|
||||
# Show license agreement first
|
||||
self._show_license_agreement()
|
||||
else:
|
||||
# Show main interface directly
|
||||
self._show_main_interface()
|
||||
|
||||
def _show_license_agreement(self):
|
||||
"""Show the license agreement interface."""
|
||||
# Adjust window size for license agreement
|
||||
self.geometry("700x600")
|
||||
self.minsize(700, 600)
|
||||
|
||||
# Create license agreement frame
|
||||
self.license_frame = LicenseAgreementFrame(self, self._on_license_agreed)
|
||||
|
||||
def _on_license_agreed(self):
|
||||
"""Handle when user agrees to license."""
|
||||
self.license_agreed = True
|
||||
|
||||
# Remove license frame
|
||||
if self.license_frame:
|
||||
self.license_frame.destroy()
|
||||
self.license_frame = None
|
||||
|
||||
# Switch to main interface
|
||||
self._show_main_interface()
|
||||
|
||||
def _show_main_interface(self):
|
||||
"""Show the main application interface."""
|
||||
# Reset window size for main interface
|
||||
self.geometry("520x320")
|
||||
self.minsize(520, 320)
|
||||
|
||||
# Setup main UI
|
||||
self._setup_main_ui()
|
||||
self._initialize_components()
|
||||
self._initialize_app()
|
||||
|
||||
def _setup_main_ui(self):
|
||||
"""Setup the main user interface."""
|
||||
# Direction selection
|
||||
self.direction_var = tk.StringVar(value="pull")
|
||||
direction_frame = tk.Frame(self)
|
||||
@@ -265,16 +335,27 @@ class AndroidFileHandlerGUI(tk.Tk):
|
||||
"""Initialize the application - check ADB and device."""
|
||||
# Check adb availability
|
||||
if not is_adb_available():
|
||||
# Show welcome message when platform-tools need to be downloaded
|
||||
messagebox.showinfo(
|
||||
"Welcome to Android File Transfer!",
|
||||
"Welcome to Android File Transfer! This application simplifies "
|
||||
"and speeds up file transfers over USB between computers and Android devices. "
|
||||
"Please do not delete or move the platform-tools folder that will be "
|
||||
"downloaded. These are tools written by Google "
|
||||
"and they are required for this application to function properly."
|
||||
)
|
||||
|
||||
self.disable_controls()
|
||||
self._update_status("ADB not found locally. Downloading...")
|
||||
self.update()
|
||||
success = self.adb_manager.download_and_extract_adb()
|
||||
self.adb_manager.download_and_extract_adb()
|
||||
success = is_adb_available()
|
||||
if success:
|
||||
self._update_status("ADB downloaded and ready.")
|
||||
self.enable_controls()
|
||||
else:
|
||||
self._update_status(
|
||||
"Failed to download ADB. Please check your internet and restart."
|
||||
"Failed to download/access Android Debug Bridge tools. Please check your internet and for any blocking security pop-ups and restart."
|
||||
)
|
||||
messagebox.showerror("Error", "Failed to download ADB tools. Exiting.")
|
||||
self.quit()
|
||||
@@ -315,6 +396,17 @@ class AndroidFileHandlerGUI(tk.Tk):
|
||||
"""Browse for local folder or file based on direction."""
|
||||
import tkinter.filedialog as fd
|
||||
import os
|
||||
from tkinter import messagebox
|
||||
|
||||
# Show helpful notification about folder selection behavior
|
||||
messagebox.showinfo(
|
||||
"File/Folder Selection Notice",
|
||||
"In this early stage of this program, if you are choosing a folder for transfer, "
|
||||
"the folder you are actually in will be the one that is chosen for transfer, "
|
||||
"not the one that you have highlighted/selected. File selection works "
|
||||
"as normal - click on it and it will be chosen for transfer. Only one folder or "
|
||||
"file may be moved at a time."
|
||||
)
|
||||
|
||||
direction = self.direction_var.get()
|
||||
|
||||
@@ -693,7 +785,7 @@ class AndroidFileHandlerGUI(tk.Tk):
|
||||
success = self.adb_manager.pull_folder(remote_path, local_path)
|
||||
if success and self.current_transfer_id == transfer_id:
|
||||
self._stop_transfer_animation()
|
||||
self._update_status("Transfer completed successfully.")
|
||||
self._update_status("Transfer completed successfully. To start another transfer, please select another file or folder.")
|
||||
self.show_disable_debugging_reminder()
|
||||
# Clear paths and disable button for next transfer
|
||||
self.after(0, self._clear_paths_and_disable_button)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to debug folder parsing
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, "src")
|
||||
|
||||
from adb_manager import ADBManager
|
||||
|
||||
|
||||
def main():
|
||||
adb = ADBManager()
|
||||
print("Testing folder parsing...")
|
||||
|
||||
result = adb.run_adb_command(["shell", "ls", "-la", "/sdcard/"])
|
||||
print(f"Return code: {result[2]}")
|
||||
|
||||
lines = result[0].strip().split("\n")
|
||||
print(f"Total lines: {len(lines)}")
|
||||
print("\nFirst 10 lines:")
|
||||
for i, line in enumerate(lines[:10]):
|
||||
print(f" {i}: {repr(line)}")
|
||||
|
||||
print("\nParsing directories:")
|
||||
folders = []
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith("d"):
|
||||
parts = line.split()
|
||||
print(f" Line {i}: {len(parts)} parts")
|
||||
if len(parts) >= 8: # Changed from 9 to 8
|
||||
folder_name = " ".join(parts[7:]) # Changed from 8 to 7
|
||||
print(f" Folder name: {repr(folder_name)}")
|
||||
if folder_name not in [".", ".."]:
|
||||
folders.append(folder_name)
|
||||
print(f" ✅ Added: {repr(folder_name)}")
|
||||
else:
|
||||
print(f" ❌ Skipped: {repr(folder_name)}")
|
||||
|
||||
print(f"\nFinal folders list ({len(folders)} items):")
|
||||
for folder in sorted(folders):
|
||||
print(f" - {repr(folder)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user