198 lines
7.6 KiB
Python
198 lines
7.6 KiB
Python
# -*- encoding=utf8 -*-
|
||
import os
|
||
import sys
|
||
import tempfile
|
||
import zipfile
|
||
|
||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||
|
||
import logging
|
||
from airtest.core.api import *
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
from config_loader import load_config as load_autool_config
|
||
from adb_helper import ADBHelper
|
||
from DroidBot.exceptions import ADBException
|
||
|
||
|
||
def _load_autool_config():
|
||
return load_autool_config()
|
||
|
||
|
||
class LocalFileImporter:
|
||
|
||
def __init__(self):
|
||
"""初始化 LocalFileImporter,创建 ADB Helper 实例"""
|
||
self.adb_helper = ADBHelper()
|
||
|
||
def get_file_paths(self, folder):
|
||
"""Get all files in folder"""
|
||
if not os.path.exists(folder):
|
||
return []
|
||
return sorted(
|
||
[
|
||
os.path.join(folder, f)
|
||
for f in os.listdir(folder)
|
||
if os.path.isfile(os.path.join(folder, f))
|
||
]
|
||
)
|
||
|
||
def _sort_apk_files(self, apk_files):
|
||
return sorted(
|
||
apk_files,
|
||
key=lambda path: (
|
||
os.path.basename(path).lower() != 'base.apk',
|
||
os.path.basename(path).lower(),
|
||
),
|
||
)
|
||
|
||
def _collect_recursive_files(self, folder, suffix):
|
||
matches = []
|
||
suffix_lower = suffix.lower()
|
||
for root, _, files in os.walk(folder):
|
||
for file_name in files:
|
||
if file_name.lower().endswith(suffix_lower):
|
||
matches.append(os.path.join(root, file_name))
|
||
return self._sort_apk_files(matches) if suffix_lower == '.apk' else sorted(matches)
|
||
|
||
def _install_apk_files(self, package_name, apk_files):
|
||
if len(apk_files) == 0:
|
||
logger.info('No APK files found to install')
|
||
return False, "no apk files"
|
||
|
||
try:
|
||
if len(apk_files) > 1:
|
||
logger.info(f"Installing {len(apk_files)} APK files...")
|
||
self.adb_helper.install_multiple(apk_files)
|
||
else:
|
||
logger.info("Installing single APK file...")
|
||
self.adb_helper.install(apk_files[0])
|
||
logger.info(f"Import success: {package_name}")
|
||
return True, "success"
|
||
except ADBException as e:
|
||
logger.error(f"ADB installation failed: {e}")
|
||
if 'not connected' in str(e).lower():
|
||
raise
|
||
return False, f"install failed: {e}"
|
||
except Exception as e:
|
||
logger.error(f"Error during import process: {e}")
|
||
return False, str(e)
|
||
|
||
def _push_obb_files(self, package_name, obb_files):
|
||
if not obb_files:
|
||
return
|
||
|
||
remote_dir = f"/sdcard/Android/obb/{package_name}"
|
||
logger.info(f"Preparing OBB directory: {remote_dir}")
|
||
self.adb_helper.shell(f"mkdir -p {remote_dir}")
|
||
for obb_path in obb_files:
|
||
logger.info(f"Pushing OBB file: {obb_path}")
|
||
self.adb_helper.run_cmd(['push', obb_path, remote_dir])
|
||
|
||
def _import_xapk(self, package_name, xapk_path):
|
||
logger.info(f"Importing XAPK for {package_name}: {xapk_path}")
|
||
try:
|
||
with tempfile.TemporaryDirectory(prefix=f"{package_name.replace('.', '_')}_xapk_") as temp_dir:
|
||
with zipfile.ZipFile(xapk_path, 'r') as archive:
|
||
archive.extractall(temp_dir)
|
||
|
||
apk_files = self._collect_recursive_files(temp_dir, '.apk')
|
||
if not apk_files:
|
||
logger.error("No APK files found inside XAPK")
|
||
return False, "xapk missing apk files"
|
||
|
||
obb_files = self._collect_recursive_files(temp_dir, '.obb')
|
||
self._push_obb_files(package_name, obb_files)
|
||
return self._install_apk_files(package_name, apk_files)
|
||
except ADBException:
|
||
raise
|
||
except zipfile.BadZipFile:
|
||
logger.error(f"Invalid XAPK archive: {xapk_path}")
|
||
return False, "invalid xapk"
|
||
except Exception as e:
|
||
logger.error(f"Error during XAPK import process: {e}")
|
||
return False, str(e)
|
||
|
||
def start(self, package_name, local_path=None, local_apk_dir=None, local_apk_files=None, **kwargs):
|
||
"""
|
||
Main entry point for Local File Importer
|
||
Installs APKs from a local directory (or network share) to the device.
|
||
|
||
Args:
|
||
package_name: Android package name
|
||
local_path: Base directory for APK storage (defaults to config LOCAL_APK_PATH)
|
||
local_apk_dir: Full path to the package-specific APK directory (from dispatcher)
|
||
When set, used directly instead of os.path.join(local_path, package_name)
|
||
local_apk_files: Optional list of expected files for validation
|
||
"""
|
||
|
||
# Determine source folder
|
||
if local_apk_dir:
|
||
folder = local_apk_dir
|
||
logger.info(f"[LocalImport] 使用中控下发的APK路径: {folder}")
|
||
else:
|
||
if local_path is None:
|
||
local_path = str(_load_autool_config()["LOCAL_APK_PATH"]).strip()
|
||
folder = os.path.join(local_path, package_name)
|
||
logger.info(f"[LocalImport] 使用默认路径: {folder}")
|
||
|
||
logger.info(f"Attempting to import {package_name} from {folder}")
|
||
logger.info(f"Folder Path: {folder}")
|
||
|
||
if not os.path.isdir(folder):
|
||
logger.error(f"Folder not found or invalid: {folder}")
|
||
return False, "folder not found"
|
||
|
||
if local_apk_files:
|
||
expected_names = {str(f.get('filename') or '').strip() for f in local_apk_files}
|
||
actual_names = {os.path.basename(p) for p in self.get_file_paths(folder)}
|
||
missing = expected_names - actual_names
|
||
if missing:
|
||
logger.warning(f"Expected APK files missing from {folder}: {missing}")
|
||
|
||
logger.info("Found files:")
|
||
for path in self.get_file_paths(folder):
|
||
logger.info(path)
|
||
|
||
apk_files = self._sort_apk_files(
|
||
[path for path in self.get_file_paths(folder) if path.lower().endswith('.apk')]
|
||
)
|
||
has_base_apk = any(os.path.basename(path).lower() == 'base.apk' for path in apk_files)
|
||
if has_base_apk:
|
||
logger.info("Detected base.apk, using APK directory import")
|
||
return self._install_apk_files(package_name, apk_files)
|
||
|
||
xapk_files = [path for path in self.get_file_paths(folder) if path.lower().endswith('.xapk')]
|
||
if not xapk_files:
|
||
logger.error("No base.apk or XAPK files found in folder")
|
||
return False, "base.apk not found and xapk missing"
|
||
|
||
xapk_path = xapk_files[0]
|
||
if len(xapk_files) > 1:
|
||
logger.warning(f"Multiple XAPK files found, using first one: {xapk_path}")
|
||
return self._import_xapk(package_name, xapk_path)
|
||
|
||
def stop(self):
|
||
"""Stop any running processes (if any)"""
|
||
# Local file import is usually blocking/synchronous in start(),
|
||
# so there isn't a long-running background process to stop.
|
||
# However, for consistency with other downloaders, we can log this.
|
||
logger.info("LocalFileImporter: stop called (no active service to stop)")
|
||
pass
|
||
|
||
if __name__ == "__main__":
|
||
package_name = "com.google.android.youtube"
|
||
local_path = r"D:\DPI\autool\saveapk"
|
||
|
||
importer = LocalFileImporter()
|
||
|
||
try:
|
||
success, message = importer.start(package_name, local_path)
|
||
if success:
|
||
print(f"Import completed successfully: {message}")
|
||
else:
|
||
print(f"Import failed: {message}")
|
||
except Exception as e:
|
||
print(f"Error: {e}")
|