1
0
mirror of https://github.com/gryf/mc_adbfs.git synced 2026-03-27 22:03:34 +01:00

4 Commits
0.10 ... 0.11

Author SHA1 Message Date
e4a4aa8974 Added Python3 compatibility 2018-03-11 16:09:28 +01:00
755ce62321 Added toybox support 2017-05-24 21:57:19 +02:00
d44050118c Added adb_connect feature 2017-05-21 20:52:56 +02:00
a216b31ef1 Fix for adb_command 2017-05-21 20:47:18 +02:00
2 changed files with 117 additions and 26 deletions

View File

@@ -8,10 +8,11 @@ This is Midnight Commander extfs plugin for browsing Android device through
Rquirements Rquirements
=========== ===========
* Python 2.7 * Python 2.7 or 3.x (tested on 3.5.4)
* ``adb`` installed and in ``$PATH`` or provided via the config file * ``adb`` installed and in ``$PATH`` or provided via the config file
* An Android device or emulator preferably rooted * An Android device or emulator preferably rooted
* Busybox installed and available in the path on the device * ``busybox`` (``toolbox``, ``toybox``) installed and available in the path on
the device
Make sure, that issuing from command line: Make sure, that issuing from command line:
@@ -70,6 +71,8 @@ You can configure behaviour of this plugin using ``.ini`` file located under
suppress_colors = false suppress_colors = false
root = root =
adb_command = adb adb_command = adb
adb_connect =
where: where:
@@ -80,11 +83,16 @@ where:
everything (slow!) everything (slow!)
* ``suppress_colors`` this option will make ``busybox`` not to display colors, * ``suppress_colors`` this option will make ``busybox`` not to display colors,
helpful, if ``busybox ls`` is configured to display colors by default. Does helpful, if ``busybox ls`` is configured to display colors by default. Does
not affect ``toolbox``. not affect ``toolbox`` or ``toybox``.
* ``root`` root directory to read. Everything outside of that directory will be * ``root`` root directory to read. Everything outside of that directory will be
omitted. That would be the fastest way to access certain location on the omitted. That would be the fastest way to access certain location on the
device. Note, that ``dirs_to_skip`` still apply inside this directory. device. Note, that ``dirs_to_skip`` still apply inside this directory.
* ``adb_command`` = path to ``adb`` command. * ``adb_command`` absolute or relative path to ``adb`` command. `~/` or
environment variables are allowed.
* ``adb_connect`` specifies if connection to specific device needs to be
performed before accessing shell. It is useful for *adb over network*
feature. Typical value here is a device IP address with optional port, which
defaults to 5555.
Limitations Limitations
=========== ===========

125
adbfs
View File

@@ -5,22 +5,35 @@ adbfs Virtual filesystem for Midnight Commander
* Copyright (c) 2016, Roman Dobosz, * Copyright (c) 2016, Roman Dobosz,
* Published under 3-clause BSD-style license (see LICENSE file) * Published under 3-clause BSD-style license (see LICENSE file)
""" """
try:
import ConfigParser import ConfigParser as configparser
except ImportError:
import configparser
import argparse import argparse
from datetime import datetime from datetime import datetime
import errno
import json import json
import os import os
import re import re
import subprocess import subprocess
import sys import sys
__version__ = 0.10 __version__ = 0.11
XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
def check_output(command_list, stderr=None):
"""
For some reason, in py3 it was decided that command output should be bytes
instead of string. This little function will check if we have string or
bytes and in case of bytes it will convert it to string.
"""
result = subprocess.check_output(command_list, stderr=stderr)
if not isinstance(result, str):
result = result.decode('utf-8')
return result
class NoBoxFoundException(OSError): class NoBoxFoundException(OSError):
""" """
Exception raised in case of not found either toolbox or busybox on remote Exception raised in case of not found either toolbox or busybox on remote
@@ -44,6 +57,16 @@ class Conf(object):
'toolbox': {'ls': 'toolbox ls -anl', 'toolbox': {'ls': 'toolbox ls -anl',
'rls': 'toolbox ls -Ranl {}', 'rls': 'toolbox ls -Ranl {}',
'file_re': r'^(?P<perms>[-bcdlps][-rwxsStT]{9})\s+' 'file_re': r'^(?P<perms>[-bcdlps][-rwxsStT]{9})\s+'
r'(?P<uid>\d+)\s+'
r'(?P<gid>\d+)\s+'
r'(?P<size>\d+)?\s'
r'(?P<date>\d{4}-\d{2}-\d{2}\s'
r'\d{2}:\d{2})\s'
r'(?P<name>.*)'},
'toybox': {'ls': 'toybox ls -anl',
'rls': 'toybox ls -Ranl {}',
'file_re': r'^(?P<perms>[-bcdlps][-rwxsStT]{9})\s+'
r'(?P<links>\d+)\s+'
r'(?P<uid>\d+)\s+' r'(?P<uid>\d+)\s+'
r'(?P<gid>\d+)\s+' r'(?P<gid>\d+)\s+'
r'(?P<size>\d+)?\s' r'(?P<size>\d+)?\s'
@@ -58,8 +81,10 @@ class Conf(object):
self.root = None self.root = None
self.suppress_colors = False self.suppress_colors = False
self.adb_command = 'adb' self.adb_command = 'adb'
self.adb_connect = ''
self.read() self.read()
self.connect()
self.get_the_box() self.get_the_box()
def get_the_box(self): def get_the_box(self):
@@ -67,8 +92,7 @@ class Conf(object):
cmd = [self.adb_command] + 'shell which'.split() cmd = [self.adb_command] + 'shell which'.split()
try: try:
with open(os.devnull, 'w') as fnull: with open(os.devnull, 'w') as fnull:
result = subprocess.check_output(cmd + ['busybox'], result = check_output(cmd + ['busybox'], stderr=fnull)
stderr=fnull)
if 'busybox' in result: if 'busybox' in result:
self.box = Conf.boxes['busybox'] self.box = Conf.boxes['busybox']
if self.suppress_colors: if self.suppress_colors:
@@ -82,8 +106,18 @@ class Conf(object):
try: try:
with open(os.devnull, 'w') as fnull: with open(os.devnull, 'w') as fnull:
result = subprocess.check_output(cmd + ['toolbox'], result = check_output(cmd + ['toybox'], stderr=fnull)
stderr=fnull)
if 'toybox' in result:
self.box = Conf.boxes['toybox']
Adb.file_re = re.compile(self.box['file_re'])
return
except subprocess.CalledProcessError:
pass
try:
with open(os.devnull, 'w') as fnull:
result = check_output(cmd + ['toolbox'], stderr=fnull)
if 'toolbox' in result: if 'toolbox' in result:
self.box = Conf.boxes['toolbox'] self.box = Conf.boxes['toolbox']
@@ -92,8 +126,56 @@ class Conf(object):
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
pass pass
raise NoBoxFoundException(errno.ENOENT, sys.stderr.write('There is no toolbox or busybox available.\n')
'There is no toolbox or busybox available') sys.exit(1)
def get_attached_devices(self):
"""Return a list of attached devices"""
cmd = [self.adb_command, 'devices']
devices = []
try:
with open(os.devnull, 'w') as fnull:
result = check_output(cmd, stderr=fnull)
except subprocess.CalledProcessError:
result = ''
for line in result.split('\n'):
if line.startswith('*'):
continue
if line.strip() == 'List of devices attached':
continue
if line.strip() == '':
continue
identifier, _ = line.split()
devices.append(identifier)
return devices
def connect(self):
"""
If adb_connect is non empty string, perform connecting to specified
device over network using an address (or hostname).
"""
if not self.adb_connect:
return
devices = self.get_attached_devices()
for device in devices:
if self.adb_connect in device:
return # already connected, no need to reconnect
cmd = [self.adb_command, 'connect', self.adb_connect]
with open(os.devnull, 'w') as fnull:
result = check_output(cmd, stderr=fnull)
if result.split()[0] == 'connected':
subprocess.call([self.adb_command, 'wait-for-device'])
return
sys.stderr.write('Unable to connect to `%s\'. Is adb over network '
'enabled on device?\n' % self.adb_connect)
sys.exit(2)
def read(self): def read(self):
""" """
@@ -107,18 +189,19 @@ class Conf(object):
if not os.path.exists(conf_fname): if not os.path.exists(conf_fname):
return return
cfg = ConfigParser.SafeConfigParser() cfg = configparser.SafeConfigParser()
cfg_map = {'debug': (cfg.getboolean, 'debug'), cfg_map = {'debug': (cfg.getboolean, 'debug'),
'dirs_to_skip': (cfg.get, 'dirs_to_skip'), 'dirs_to_skip': (cfg.get, 'dirs_to_skip'),
'suppress_colors': (cfg.get, 'suppress_colors'), 'suppress_colors': (cfg.get, 'suppress_colors'),
'root': (cfg.get, 'root'), 'root': (cfg.get, 'root'),
'adb_command': (cfg.get, 'adb')} 'adb_command': (cfg.get, 'adb_command'),
'adb_connect': (cfg.get, 'adb_connect')}
cfg.read(conf_fname) cfg.read(conf_fname)
for key, (function, attr) in cfg_map.items(): for key, (function, attr) in cfg_map.items():
try: try:
setattr(self, attr, function('adbfs', key)) setattr(self, attr, function('adbfs', key))
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): except (configparser.NoSectionError, configparser.NoOptionError):
pass pass
if self.dirs_to_skip and isinstance(self.dirs_to_skip, str): if self.dirs_to_skip and isinstance(self.dirs_to_skip, str):
@@ -259,7 +342,7 @@ class Adb(object):
cmd = [self.conf.adb_command] + 'shell su -c whoami'.split() cmd = [self.conf.adb_command] + 'shell su -c whoami'.split()
try: try:
with open(os.devnull, 'w') as fnull: with open(os.devnull, 'w') as fnull:
result = subprocess.check_output(cmd, stderr=fnull) result = check_output(cmd, stderr=fnull)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
return return
@@ -313,9 +396,9 @@ class Adb(object):
try: try:
if self.conf.debug: if self.conf.debug:
print 'executing', ' '.join(command) print('executing', ' '.join(command))
lines = subprocess.check_output(command) lines = check_output(command)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write('Cannot read directory. Is device connected?\n') sys.stderr.write('Cannot read directory. Is device connected?\n')
return 1 return 1
@@ -375,9 +458,9 @@ class Adb(object):
try: try:
if self.conf.debug: if self.conf.debug:
print 'executing', ' '.join(command) print('executing', ' '.join(command))
lines = subprocess.check_output(command) lines = check_output(command)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write('Cannot read directory. Is device connected?\n') sys.stderr.write('Cannot read directory. Is device connected?\n')
return 1 return 1
@@ -485,7 +568,7 @@ class Adb(object):
cmd = [self.conf.adb_command, 'shell', 'rm', dst] cmd = [self.conf.adb_command, 'shell', 'rm', dst]
try: try:
err = subprocess.check_output(cmd) err = check_output(cmd)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write('Error executing adb shell') sys.stderr.write('Error executing adb shell')
return 1 return 1
@@ -503,7 +586,7 @@ class Adb(object):
cmd = [self.conf.adb_command, 'shell', 'rm', '-r', dst] cmd = [self.conf.adb_command, 'shell', 'rm', '-r', dst]
try: try:
err = subprocess.check_output(cmd) err = check_output(cmd)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write('Error executing adb shell') sys.stderr.write('Error executing adb shell')
return 1 return 1
@@ -524,7 +607,7 @@ class Adb(object):
cmd = [self.conf.adb_command, 'shell', 'mkdir', dst] cmd = [self.conf.adb_command, 'shell', 'mkdir', dst]
try: try:
err = subprocess.check_output(cmd) err = check_output(cmd)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
sys.stderr.write('Error executing adb shell') sys.stderr.write('Error executing adb shell')
return 1 return 1