Updated user instructions and added license agreement

This commit is contained in:
2025-08-25 11:18:48 -05:00
committed by Jason Ross
parent 1a36dca586
commit 589398b1a5
8 changed files with 368 additions and 203 deletions
+1
View File
@@ -0,0 +1 @@
1
Generated
+2 -120
View File
@@ -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
View File
@@ -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
View File
@@ -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
+191
View File
@@ -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
View File
@@ -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)
+1
View File
@@ -0,0 +1 @@
1
-47
View File
@@ -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()