From 5ece2d579c730ec36951a19ac5a9d1c5b2194990 Mon Sep 17 00:00:00 2001 From: gryf Date: Tue, 14 May 2019 21:10:28 +0200 Subject: [PATCH] Fix bad filenames for Python3. In Python3, if there are filenames encoded with 8-bit encodings, there might be an issues with converting them into unicode objects. This is a workaround on this subject. Python2 is not affected. Other than that, there was tests added to cover this case, appropriate Makefile which automate creating venvs for both: Python 2 and 3, and also there is a check against pep8 rules using flake8. --- .gitignore | 3 +++ Makefile | 55 +++++++++++++++++++++++++++++++++++++++ README.rst | 48 +++++++++++++++++++++++++++++++--- adbfs | 12 +++++++-- test_adbfs.py | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 test_adbfs.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d179e31 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.test +__pycache__ +adbfsc diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ba6fac2 --- /dev/null +++ b/Makefile @@ -0,0 +1,55 @@ +# simple makefile for running tests for the adbfs plugin + +all: test_dir py2 py3 flake8 + +TEST_DIR='.test' +PY2_VENV=$(TEST_DIR)/py2 +PY3_VENV=$(TEST_DIR)/py3 +FL8_VENV=$(TEST_DIR)/flake8 +TST_EXISTS=$(shell [ -e $(TEST_DIR) ] && echo 1 || echo 0) +PY2_EXISTS=$(shell [ -e $(PY2_VENV) ] && echo 1 || echo 0) +PY3_EXISTS=$(shell [ -e $(PY3_VENV) ] && echo 1 || echo 0) +FL8_EXISTS=$(shell [ -e $(FL8_VENV) ] && echo 1 || echo 0) + +py3: test_dir virtualenv3 + .test/py3/bin/python test_adbfs.py + +py2: test_dir virtualenv2 + .test/py2/bin/python test_adbfs.py + +flake8: test_dir virtualenv_flake8 + .test/flake8/bin/flake8 adbfs test_adbfs.py + +ifeq ($(TST_EXISTS), 0) +test_dir: + mkdir -p .test +else +test_dir: +endif + +ifeq ($(PY3_EXISTS), 0) +virtualenv3: + virtualenv -p python3 $(PY3_VENV) + $(PY3_VENV)/bin/pip install six +else +virtualenv3: +endif + +ifeq ($(PY2_EXISTS), 0) +virtualenv2: + virtualenv -p python2 $(PY2_VENV) + $(PY2_VENV)/bin/pip install mock +else +virtualenv2: +endif + +ifeq ($(FL8_EXISTS), 0) +virtualenv_flake8: + virtualenv -p python2 $(FL8_VENV) + $(FL8_VENV)/bin/pip install flake8 +else +virtualenv_flake8: +endif + +clean: + rm -fr $(TEST_DIR) __pycache__ adbfsc diff --git a/README.rst b/README.rst index c4a5c5f..db9ac77 100644 --- a/README.rst +++ b/README.rst @@ -5,6 +5,7 @@ Midnight Commander adbfs external fs plugin This is Midnight Commander extfs plugin for browsing Android device through ``adb`` interface written in Python. + Rquirements =========== @@ -24,6 +25,7 @@ Make sure, that issuing from command line: it should display files from root directory on the device. + Features ======== @@ -35,12 +37,14 @@ Features * Symbolic links in lists are corrected to be relative to the file system * Symbolic links also point to the right target, skipping intermediate links + Installation ============ Copy adbfs into ``~/.local/share/mc/extfs.d/`` directory and make it executable if needed. + Usage ===== @@ -48,13 +52,14 @@ To use it, just issue: .. code:: shell-session - cd adbfs:// + $ cd adbfs:// under MC - after some time you should see the files and directories on your -device. For convenience you can add a bookmark (accessible under CTRL+\) for +device. For convenience you can add a bookmark (accessible under CTRL+\\) for fast access. The time is depended on how many files and directories you have on your device and how fast it is :) + Configuration ============= @@ -72,7 +77,6 @@ You can configure behaviour of this plugin using ``.ini`` file located under adb_command = adb adb_connect = - where: * ``debug`` will provide a little bit more verbose information, useful for @@ -93,6 +97,43 @@ where: feature. Typical value here is a device IP address with optional port, which defaults to 5555. + +Contribution +============ + +There is a ``Makefile`` in the top directory, which is basic helper for running +the tests. Please use it, and adapt/add tests for provided fixes/functionality. + +It requires GNU ``make`` program, and also ``virtualenv`` besides Python in +version 2 and 3. Using it is simple as running following command: + +.. code:: shell-session + + $ make + +it will run `py2`, `py3` and `flake8` jobs to check it against the code. For +running tests against Python 3: + +.. code:: shell-session + + $ make py3 + +or Python 2: + +.. code:: shell-session + + $ make py2 + +or flake 8: + +.. code:: shell-session + + $ make flake8 + +Exit status on any of those means that test fail. Appropriate message/traceback +will also be visible. + + Limitations =========== @@ -103,6 +144,7 @@ Limitations * The implementation is experimental and it's by now working with mine device; while it might not work with yours + License ======= diff --git a/adbfs b/adbfs index e99225a..ea681d9 100755 --- a/adbfs +++ b/adbfs @@ -17,7 +17,7 @@ import re import subprocess import sys -__version__ = 0.11 +__version__ = 0.12 XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) @@ -30,7 +30,15 @@ def check_output(command_list, stderr=None): """ result = subprocess.check_output(command_list, stderr=stderr) if not isinstance(result, str): - result = result.decode('utf-8') + _result = [] + for t in result.split(b'\n'): + if not t: + continue + try: + _result.append(t.decode('utf-8')) + except UnicodeDecodeError: + _result.append(t.decode('iso-8859-1')) + result = '\n'.join(_result) + '\n' return result diff --git a/test_adbfs.py b/test_adbfs.py new file mode 100644 index 0000000..a105ce9 --- /dev/null +++ b/test_adbfs.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +import os +import six +import unittest +try: + from unittest import mock +except ImportError: + import mock + +FILE = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'adbfs') + +try: + from importlib.util import spec_from_loader, module_from_spec + from importlib.machinery import SourceFileLoader + spec = spec_from_loader("adbfs", SourceFileLoader("adbfs", FILE)) + adbfs = module_from_spec(spec) + spec.loader.exec_module(adbfs) +except ImportError: + # py27 + import imp + adbfs = imp.load_source('adbfs', FILE) + +LISTING = '''\ +-rw-rw---- 1 0 1015 0 01/01/2010 22:11:01 /storage/emulated/0/Grüß Gott +-rw-rw---- 1 0 1015 0 01/01/2010 22:11:01 /storage/emulated/0/\x80 +-rw-rw---- 1 0 1015 0 01/01/2010 22:11:01 /storage/emulated/0/Γεια σας +-rw-rw---- 1 0 1015 0 01/01/2010 22:11:01 /storage/emulated/0/Здравствуйте +-rw-rw---- 1 0 1015 0 01/01/2010 22:11:01 /storage/emulated/0/שָׁלוֹם +-rw-rw---- 1 0 1015 0 01/01/2010 22:11:01 /storage/emulated/0/السَّلامُ عَلَيْكُمْ +-rw-rw---- 1 0 1015 0 01/01/2010 22:11:01 /storage/emulated/0/გამარჯობა +-rw-rw---- 1 0 1015 0 01/01/2010 22:11:01 /storage/emulated/0/こんにちは。 +-rw-rw---- 1 0 1015 0 01/01/2010 22:11:01 /storage/emulated/0/안녕하십니까 +''' # noqa + + +class TestCheckOutput(unittest.TestCase): + + @mock.patch('subprocess.check_output') + def test_check_output(self, out): + """ + As for Python2 (and its last version: 2.7), subprocess.check_output + always return string like objects, contrary to bytes - no conversion + to string is needed. + Python3 treats string as unicode objects, but subprocess.check_output + returns bytes object, which is equvalend for py2 string… annoying. + """ + if six.PY3: + out.return_value = bytes(LISTING, 'utf-8') + else: + out.return_value = LISTING + result = adbfs.check_output(None) + self.assertEqual(result, LISTING) + + @mock.patch('subprocess.check_output') + def test_check_output_py3_invalid_char(self, out): + """ + Special case for py3. We have bytes with some weird character - like + some system write something with codepage, instead of utf8. + """ + if six.PY2: + # doesn't affect Python 2 + return + line = (b'-rw-rw---- 1 0 1015 0 01/01/2010 22:11:01 ' + b'/storage/emulated/0/\xe2\n') # Latin 1 char â + out.return_value = bytes(line) + result = adbfs.check_output(None) + self.assertEqual(result, line.decode('iso-8859-1')) + + +if __name__ == "__main__": + unittest.main()