Git Product home page Git Product logo

adbfileexplorer's Introduction

ADB File Explorer

Python linting: pylint

Simple File Explorer for adb devices. Uses python library adb-shell or command-line tool adb.

Features:

  • List of adb devices
  • Connect via IP (TCP)
  • Listing / Pulling / Pushing files
  • Renaming and Deleting files

Screenshots

Devices & Notifications

Devices & Notifications

Files

Files

Requirements

  • Python3 (below version 3.8 not tested)
sudo apt-get install python3-pip python3-pyqt5  # For Linux Ubuntu
pip install PyQt5 libusb1 adb-shell
  • adb (binary) should exist in project root folder or in $PATH

Launch

  1. Clone the repo
  2. cd ADBFileExplorer
  3. Edit settings.json from the project root if necessary. src/app/settings.json
{
  "adb_path": "adb",
//  "adb_core": "external",
  "adb_kill_server_at_exit": false,
  "preserve_timestamp": true,
  "adb_run_as_root": false
}
  • adb_path - Full adb path or just 'adb' if the executable is in $PATH
  • adb_core - Set to 'external' to use external adb executable, otherwise the app will use adb-shell
# First install python-venv in root folder. It should be like ADBFileExplorer/venv
pip install -r requirements.txt
run.bat # To start application on Windows
bash run.sh # To start application on Linux...

Attention

Application uses by default adb-shell. There may be problems with listing, pushing, or pulling files using adb-shell. For a better experience, try adding "adb_core": "external" to settings.json.

License

ADB File Explorer [python-app]
Copyright (C) 2022  Azat Aldeshov

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

adbfileexplorer's People

Contributors

aldeshov avatar ctrlcctrlv avatar dandv avatar keenox avatar tomiwa-ot avatar werve avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

adbfileexplorer's Issues

WSA support

Adding support for "Windows subsystem for android" in ADBFileExplorer.

We all know that transferring files to Windows subsystem for android is a hassle.
There is already a script which pushes files to WSA using adb: EasySideload-WSA
It works but it isn't as near as ADBFileExplorer, you can't browse WSA files or even pull them and it doesn't have a GUI, I think it may be useful to get an idea for how to add WSA support in ADBFileExplorer.
So adding support for WSA in ADBFileExplorer would be very useful.
Thanks.

Preserve timestamp

I would like to suggest that it would be better to transfer to PC (pull) with the option adb pull -a which preserves file timestamps.

modified main.py file to enable select feature

`# ADB File Explorer

Copyright (C) 2

file_selection_action = QAction('Select Multiple Files', self)
file_selection_action.triggered.connect(self.select_multiple_files)
self.file_menu.addAction(file_selection_action)
022 Azat Aldeshov
import sys
from typing import Any

from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import Qt, QPoint, QModelIndex, QAbstractListModel, QVariant, QRect, QSize, QEvent, QObject
from PyQt5.QtGui import QPixmap, QColor, QPalette, QMovie, QKeySequence
from PyQt5.QtWidgets import QMenu, QAction, QMessageBox, QFileDialog, QStyle, QWidget, QStyledItemDelegate,
QStyleOptionViewItem, QApplication, QListView, QVBoxLayout, QLabel, QSizePolicy, QHBoxLayout, QTextEdit,
QMainWindow

from app.core.configurations import Resources
from app.core.main import Adb
from app.core.managers import Global
from app.data.models import FileType, MessageData, MessageType
from app.data.repositories import FileRepository
from app.gui.explorer.toolbar import ParentButton, UploadTools, PathBar
from app.helpers.tools import AsyncRepositoryWorker, ProgressCallbackHelper, read_string_from_file

class FileHeaderWidget(QWidget):
def init(self, parent=None):
super(FileHeaderWidget, self).init(parent)
self.setLayout(QHBoxLayout(self))
policy = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)

    self.file = QLabel('File', self)
    self.file.setContentsMargins(45, 0, 0, 0)
    policy.setHorizontalStretch(39)
    self.file.setSizePolicy(policy)
    self.layout().addWidget(self.file)

    self.permissions = QLabel('Permissions', self)
    self.permissions.setAlignment(Qt.AlignCenter)
    policy.setHorizontalStretch(18)
    self.permissions.setSizePolicy(policy)
    self.layout().addWidget(self.permissions)

    self.size = QLabel('Size', self)
    self.size.setAlignment(Qt.AlignCenter)
    policy.setHorizontalStretch(21)
    self.size.setSizePolicy(policy)
    self.layout().addWidget(self.size)

    self.date = QLabel('Date', self)
    self.date.setAlignment(Qt.AlignCenter)
    policy.setHorizontalStretch(22)
    self.date.setSizePolicy(policy)
    self.layout().addWidget(self.date)

    self.setStyleSheet("QWidget { background-color: #E5E5E5; font-weight: 500; border: 1px solid #C0C0C0 }")

class FileExplorerToolbar(QWidget):
def init(self, parent=None):
super(FileExplorerToolbar, self).init(parent)
self.setLayout(QHBoxLayout(self))
policy = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)
policy.setHorizontalStretch(1)

    self.upload_tools = UploadTools(self)
    self.upload_tools.setSizePolicy(policy)
    self.layout().addWidget(self.upload_tools)

    self.parent_button = ParentButton(self)
    self.parent_button.setSizePolicy(policy)
    self.layout().addWidget(self.parent_button)

    self.path_bar = PathBar(self)
    policy.setHorizontalStretch(8)
    self.path_bar.setSizePolicy(policy)
    self.layout().addWidget(self.path_bar)

class FileItemDelegate(QStyledItemDelegate):
def sizeHint(self, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> QtCore.QSize:
result = super(FileItemDelegate, self).sizeHint(option, index)
result.setHeight(40)
return result

def setEditorData(self, editor: QWidget, index: QtCore.QModelIndex):
    editor.setText(index.model().data(index, Qt.EditRole))

def updateEditorGeometry(self, editor: QWidget, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex):
    editor.setGeometry(
        option.rect.left() + 48, option.rect.top(), int(option.rect.width() / 2.5) - 55, option.rect.height()
    )

def setModelData(self, editor: QWidget, model: QtCore.QAbstractItemModel, index: QtCore.QModelIndex):
    model.setData(index, editor.text(), Qt.EditRole)

@staticmethod
def paint_line(painter: QtGui.QPainter, color: QColor, x, y, w, h):
    painter.setPen(color)
    painter.drawLine(x, y, w, h)

@staticmethod
def paint_text(painter: QtGui.QPainter, text: str, color: QColor, options, x, y, w, h):
    painter.setPen(color)
    painter.drawText(QRect(x, y, w, h), options, text)

def paint(self, painter: QtGui.QPainter, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex):
    if not index.data():
        return super(FileItemDelegate, self).paint(painter, option, index)

    self.initStyleOption(option, index)
    style = option.widget.style() if option.widget else QApplication.style()
    style.drawControl(QStyle.CE_ItemViewItem, option, painter, option.widget)

    line_color = QColor("#CCCCCC")
    text_color = option.palette.color(QPalette.Normal, QPalette.Text)

    top = option.rect.top()
    bottom = option.rect.height()

    first_start = option.rect.left() + 50
    second_start = option.rect.left() + int(option.rect.width() / 2.5)
    third_start = option.rect.left() + int(option.rect.width() / 1.75)
    fourth_start = option.rect.left() + int(option.rect.width() / 1.25)
    end = option.rect.width() + option.rect.left()

    self.paint_text(
        painter, index.data().name, text_color, option.displayAlignment,
        first_start, top, second_start - first_start - 4, bottom
    )

    self.paint_line(painter, line_color, second_start - 2, top, second_start - 1, bottom)

    self.paint_text(
        painter, index.data().permissions, text_color, Qt.AlignCenter | option.displayAlignment,
        second_start, top, third_start - second_start - 4, bottom
    )

    self.paint_line(painter, line_color, third_start - 2, top, third_start - 1, bottom)

    self.paint_text(
        painter, index.data().size, text_color, Qt.AlignCenter | option.displayAlignment,
        third_start, top, fourth_start - third_start - 4, bottom
    )

    self.paint_line(painter, line_color, fourth_start - 2, top, fourth_start - 1, bottom)

    self.paint_text(
        painter, index.data().date, text_color, Qt.AlignCenter | option.displayAlignment,
        fourth_start, top, end - fourth_start, bottom
    )

class FileListModel(QAbstractListModel):
def init(self, parent=None):
super().init(parent)
self.items = []

def clear(self):
    self.beginResetModel()
    self.items.clear()
    self.endResetModel()

def populate(self, files: list):
    self.beginResetModel()
    self.items.clear()
    self.items = files
    self.endResetModel()

def rowCount(self, parent: QModelIndex = ...) -> int:
    return len(self.items)

def icon_path(self, index: QModelIndex = ...):
    file_type = self.items[index.row()].type
    if file_type == FileType.DIRECTORY:
        return Resources.icon_folder
    elif file_type == FileType.FILE:
        return Resources.icon_file
    elif file_type == FileType.LINK:
        link_type = self.items[index.row()].link_type
        if link_type == FileType.DIRECTORY:
            return Resources.icon_link_folder
        elif link_type == FileType.FILE:
            return Resources.icon_link_file
        return Resources.icon_link_file_unknown
    return Resources.icon_file_unknown

def flags(self, index: QModelIndex) -> Qt.ItemFlags:
    if not index.isValid():
        return Qt.NoItemFlags

    return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable

def setData(self, index: QModelIndex, value: Any, role: int = ...) -> bool:
    if role == Qt.EditRole and value:
        data, error = FileRepository.rename(self.items[index.row()], value)
        if error:
            Global().communicate.notification.emit(
                MessageData(
                    timeout=10000,
                    title="Rename",
                    body="<span style='color: red; font-weight: 600'> %s </span>" % error,
                )
            )
        Global.communicate.files__refresh.emit()
    return super(FileListModel, self).setData(index, value, role)

def data(self, index: QModelIndex, role: int = ...) -> Any:
    if not index.isValid():
        return QVariant()

    if role == Qt.DisplayRole:
        return self.items[index.row()]
    elif role == Qt.EditRole:
        return self.items[index.row()].name
    elif role == Qt.DecorationRole:
        return QPixmap(self.icon_path(index)).scaled(32, 32, Qt.KeepAspectRatio)
    return QVariant()

class FileExplorerWidget(QWidget):
FILES_WORKER_ID = 300
DOWNLOAD_WORKER_ID = 399

def __init__(self, parent=None):
    super(FileExplorerWidget, self).__init__(parent)
    self.main_layout = QVBoxLayout(self)

    self.toolbar = FileExplorerToolbar(self)
    self.main_layout.addWidget(self.toolbar)

    self.header = FileHeaderWidget(self)
    self.main_layout.addWidget(self.header)

    self.list = QListView(self)
    self.model = FileListModel(self.list)

    self.list.setSpacing(1)
    self.list.setModel(self.model)
    self.list.installEventFilter(self)
    self.list.doubleClicked.connect(self.open)
    self.list.setItemDelegate(FileItemDelegate(self.list))
    self.list.setContextMenuPolicy(Qt.CustomContextMenu)
    self.list.customContextMenuRequested.connect(self.context_menu)
    self.list.setStyleSheet(read_string_from_file(Resources.style_file_list))
    self.list.setSelectionMode(QListView.SelectionMode.ExtendedSelection)
    self.layout().addWidget(self.list)

    self.loading = QLabel(self)
    self.loading.setAlignment(Qt.AlignCenter)
    self.loading_movie = QMovie(Resources.anim_loading, parent=self.loading)
    self.loading_movie.setScaledSize(QSize(48, 48))
    self.loading.setMovie(self.loading_movie)
    self.main_layout.addWidget(self.loading)

    self.empty_label = QLabel("Folder is empty", self)
    self.empty_label.setAlignment(Qt.AlignCenter)
    self.empty_label.setStyleSheet("color: #969696; border: 1px solid #969696")
    self.layout().addWidget(self.empty_label)

    self.main_layout.setStretch(self.layout().count() - 1, 1)
    self.main_layout.setStretch(self.layout().count() - 2, 1)

    self.text_view_window = None
    self.setLayout(self.main_layout)

    Global().communicate.files__refresh.connect(self.update)

@property
def file(self):
    if self.list and self.list.currentIndex():
        return self.model.items[self.list.currentIndex().row()]

@property
def files(self):
    if self.list and len(self.list.selectedIndexes()) > 0:
        return map(lambda index: self.model.items[index.row()], self.list.selectedIndexes())

def update(self):
    super(FileExplorerWidget, self).update()
    worker = AsyncRepositoryWorker(
        name="Files",
        worker_id=self.FILES_WORKER_ID,
        repository_method=FileRepository.files,
        response_callback=self._async_response,
        arguments=()
    )
    if Adb.worker().work(worker):
        # First Setup loading view
        self.model.clear()
        self.list.setHidden(True)
        self.loading.setHidden(False)
        self.empty_label.setHidden(True)
        self.loading_movie.start()

        # Then start async worker
        worker.start()
        Global().communicate.path_toolbar__refresh.emit()

def close(self) -> bool:
    Global().communicate.files__refresh.disconnect()
    return super(FileExplorerWidget, self).close()

def _async_response(self, files: list, error: str):
    self.loading_movie.stop()
    self.loading.setHidden(True)

    if error:
        print(error, file=sys.stderr)
        if not files:
            Global().communicate.notification.emit(
                MessageData(
                    title='Files',
                    timeout=15000,
                    body="<span style='color: red; font-weight: 600'> %s </span>" % error
                )
            )
    if not files:
        self.empty_label.setHidden(False)
    else:
        self.list.setHidden(False)
        self.model.populate(files)
        self.list.setFocus()

def eventFilter(self, obj: 'QObject', event: 'QEvent') -> bool:
    if obj == self.list and \
            event.type() == QEvent.KeyPress and \
            event.matches(QKeySequence.InsertParagraphSeparator) and \
            not self.list.isPersistentEditorOpen(self.list.currentIndex()):
        self.open(self.list.currentIndex())
    return super(FileExplorerWidget, self).eventFilter(obj, event)

def open(self, index: QModelIndex = ...):
    if Adb.manager().open(self.model.items[index.row()]):
        Global().communicate.files__refresh.emit()

def context_menu(self, pos: QPoint):
    menu = QMenu()
    menu.addSection("Actions")

    action_copy = QAction('Copy to...', self)
    action_copy.setDisabled(True)
    menu.addAction(action_copy)

    action_move = QAction('Move to...', self)
    action_move.setDisabled(True)
    menu.addAction(action_move)

    action_rename = QAction('Rename', self)
    action_rename.triggered.connect(self.rename)
    menu.addAction(action_rename)

    action_open_file = QAction('Open', self)
    action_open_file.triggered.connect(self.open_file)
    menu.addAction(action_open_file)

    action_delete = QAction('Delete', self)
    action_delete.triggered.connect(self.delete)
    menu.addAction(action_delete)

    action_download = QAction('Download', self)
    action_download.triggered.connect(self.download_files)
    menu.addAction(action_download)

    action_download_to = QAction('Download to...', self)
    action_download_to.triggered.connect(self.download_to)
    menu.addAction(action_download_to)

    menu.addSeparator()

    action_properties = QAction('Properties', self)
    action_properties.triggered.connect(self.file_properties)
    menu.addAction(action_properties)

    menu.exec(self.mapToGlobal(pos))

@staticmethod
def default_response(data, error):
    if error:
        Global().communicate.notification.emit(
            MessageData(
                title='Download error',
                timeout=15000,
                body="<span style='color: red; font-weight: 600'> %s </span>" % error
            )
        )
    if data:
        Global().communicate.notification.emit(
            MessageData(
                title='Downloaded',
                timeout=15000,
                body=data
            )
        )

def rename(self):
    self.list.edit(self.list.currentIndex())

def open_file(self):
    # QDesktopServices.openUrl(QUrl.fromLocalFile("downloaded_path")) open via external app
    if not self.file.isdir:
        data, error = FileRepository.open_file(self.file)
        if error:
            Global().communicate.notification.emit(
                MessageData(
                    title='File',
                    timeout=15000,
                    body="<span style='color: red; font-weight: 600'> %s </span>" % error
                )
            )
        else:
            self.text_view_window = TextView(self.file.name, data)
            self.text_view_window.show()

def delete(self):
    file_names = ', '.join(map(lambda f: f.name, self.files))
    reply = QMessageBox.critical(
        self,
        'Delete',
        "Do you want to delete '%s'? It cannot be undone!" % file_names,
        QMessageBox.Yes | QMessageBox.No, QMessageBox.No
    )

    if reply == QMessageBox.Yes:
        for file in self.files:
            data, error = FileRepository.delete(file)
            if data:
                Global().communicate.notification.emit(
                    MessageData(
                        timeout=10000,
                        title="Delete",
                        body=data,
                    )
                )
            if error:
                Global().communicate.notification.emit(
                    MessageData(
                        timeout=10000,
                        title="Delete",
                        body="<span style='color: red; font-weight: 600'> %s </span>" % error,
                    )
                )
        Global.communicate.files__refresh.emit()

def download_to(self):
    dir_name = QFileDialog.getExistingDirectory(self, 'Download to', '~')
    if dir_name:
        self.download_files(dir_name)

def download_files(self, destination: str = None):
    for file in self.files:
        helper = ProgressCallbackHelper()
        worker = AsyncRepositoryWorker(
            worker_id=self.DOWNLOAD_WORKER_ID,
            name="Download",
            repository_method=FileRepository.download,
            response_callback=self.default_response,
            arguments=(
                helper.progress_callback.emit, file.path, destination
            )
        )
        if Adb.worker().work(worker):
            Global().communicate.notification.emit(
                MessageData(
                    title="Downloading to",
                    message_type=MessageType.LOADING_MESSAGE,
                    message_catcher=worker.set_loading_widget
                )
            )
            helper.setup(worker, worker.update_loading_widget)
            worker.start()

def file_properties(self):
    file, error = FileRepository.file(self.file.path)
    file = file if file else self.file

    if error:
        Global().communicate.notification.emit(
            MessageData(
                timeout=10000,
                title="Opening folder",
                body="<span style='color: red; font-weight: 600'> %s </span>" % error,
            )
        )

    info = "<br/><u><b>%s</b></u><br/>" % str(file)
    info += "<pre>Name:        %s</pre>" % file.name or '-'
    info += "<pre>Owner:       %s</pre>" % file.owner or '-'
    info += "<pre>Group:       %s</pre>" % file.group or '-'
    info += "<pre>Size:        %s</pre>" % file.raw_size or '-'
    info += "<pre>Permissions: %s</pre>" % file.permissions or '-'
    info += "<pre>Date:        %s</pre>" % file.raw_date or '-'
    info += "<pre>Type:        %s</pre>" % file.type or '-'

    if file.type == FileType.LINK:
        info += "<pre>Links to:    %s</pre>" % file.link or '-'

    properties = QMessageBox(self)
    properties.setStyleSheet("background-color: #DDDDDD")
    properties.setIconPixmap(
        QPixmap(self.model.icon_path(self.list.currentIndex())).scaled(128, 128, Qt.KeepAspectRatio)
    )
    properties.setWindowTitle('Properties')
    properties.setInformativeText(info)
    properties.exec_()

class TextView(QMainWindow):
def init(self, filename, data):
QMainWindow.init(self)

    self.setMinimumSize(QSize(500, 300))
    self.setWindowTitle(filename)

    self.text_edit = QTextEdit(self)
    self.setCentralWidget(self.text_edit)
    self.text_edit.insertPlainText(data)
    self.text_edit.move(10, 10)

def select_multiple_files(self):
file_names, _ = QFileDialog.getOpenFileNames(self, 'Select Files')
if file_names:
self.handle_selected_files(file_names)

def handle_selected_files(self, file_names: list):
# Implement your logic here
# For now, let's just print the selected files
print("Selected files:", file_names)
`
modified_files.zip

Better readme

Hi, I just installed your tool and I think the readme should be improved.

  • You state "adb (binary) should exist in project root folder". It's unclear from the doc what this is, since when getting the source zip, the root folder is the one with the readme.md file. Having adb there does not work, it needs to be in the src folder.
  • You state one should use adb binary. You do not write, that I have to uncomment adb.set_core(Adb.EXTERNAL_TOOL_ADB) to do that. Also I was under the impression I would not have to install adb-shell when using adb binary, yet I have to do that. Please write in the readme, that you have to uncomment that line, or build something that autodetects adb binary or let one choose from within the program.

Thank you

App has no root access

When accessing some root folders with the file manger (e.g. /efs) there are no files shown inside...!? it seems the app has adb without root - only adb (root) shell works...!?

Crash on too many open files

When attempting to download a lot of files (e.g. from Camera), the app crashes with:

(process:64670): GLib-ERROR **: 01:46:40.739: Creating pipes for GWakeup: Too many open files
./run.sh: line 8: 64670 Trace/breakpoint trap (core dumped) python ./src/app

I was using adb-shell because external adb was unable to read the device at all:

image

There were no other adb processes running and the app had started fine (except for lacking venv):

$ ./run.sh
./run.sh: line 1: ./venv/bin/activate: No such file or directory
Android Debug Bridge version 1.0.41
Version 28.0.2-debian
Installed as /usr/lib/android-sdk/platform-tools/adb

* daemon not running; starting now at tcp:5037
* daemon started successfully

ADB server running...

Didn't work for me.

Downloaded and installed Windows version, double-clicked on the .exe
It shows my phone as connected, but with a red icon overlaying it and I cannot explore.

Dark theme support

The program seems to have hard coded colors, which works well as long as you have a light theme configured for Qt apps, but breaks once you use a dark theme. I attached a screenshot of the program using the "Breeze dark" theme on KDE Plasma:

image

Module not found

I got the following error:

from PyQt5.QtWidgets import QApplication
ImportError: No module named PyQt5.QtWidgets

Can't delete or rename files or folders with parentheses in their names

Whenever I try deleting or renaming a file or folder with parentheses in its name while it's on my phone, the operation fails with the error "/system/bin/sh: syntax error: unexpected '('". According to a couple of posts I found on Stack Exchange, this is apparently because dash is being used instead of bash. Idk how to fix this, assuming this is truly an issue on my end.

I'm running this on Windows 10 x64, and "python -VV" reports "Python 3.8.5 (tags/v3.8.5:580fbb0, Jul 20 2020, 15:57:54) [MSC v.1924 64 bit (AMD64)]"

My src/app/settings.json is as follows:

{
  "adb_path": "adb",
  "adb_core": "external",
  "adb_kill_server_at_exit": false,
  "preserve_timestamp": true,
  "adb_run_as_root": false
}

I've tried running it with "adb_core": "external", commented out or removed, but it gave me the following error in the console window:

─────────────────────────────────
ADB File Explorer v1.3.0
Copyright (C) 2022 Azat Aldeshov
─────────────────────────────────
Platform Windows-10-10.0.19041-SP0
adb server stopped.
Using Python "adb-shell" version 0.4.3
ERROR:root:Unexpected error=LIBUSB_ERROR_NOT_SUPPORTED [-12], type(error)=<class 'usb1.USBErrorNotSupported'>
Traceback (most recent call last):
  File "C:\Other Programs\ADB File Explorer\src\app\data\repositories\python_adb.py", line 226, in devices
    device_id = device.getSerialNumber()
  File "C:\Other Programs\ADB File Explorer\venv\lib\site-packages\usb1\__init__.py", line 2019, in getSerialNumber
    return self.open().getSerialNumber()
  File "C:\Other Programs\ADB File Explorer\venv\lib\site-packages\usb1\__init__.py", line 2055, in open
    mayRaiseUSBError(libusb1.libusb_open(self.device_p, byref(handle)))
  File "C:\Other Programs\ADB File Explorer\venv\lib\site-packages\usb1\__init__.py", line 127, in mayRaiseUSBError
    __raiseUSBError(value)
  File "C:\Other Programs\ADB File Explorer\venv\lib\site-packages\usb1\__init__.py", line 119, in raiseUSBError
    raise __STATUS_TO_EXCEPTION_DICT.get(value, __USBError)(value)
usb1.USBErrorNotSupported: LIBUSB_ERROR_NOT_SUPPORTED [-12]

existing Files

Hello,

thanks for ADB File Explorer - so much faster than the bloddy MTP...
One question: What does ADB_FE do if I "download to..." a folder and the Folder already exists with some files already in it (e.g. from a previous transfer). Will it overwrite the existing files or skip them?

Regards,
Hendrik

Progress bar for pull and push

Hi!

I love ADBFileExplorer! There is no progress bar when pushing to or pulling from the device, however. Also, I've noticed that you are using a custom wrapper around adb instead of a library like python-adb, which has this functionality implemented: you can get transfer progress when pushing and pulling.

So, you could use python-adb to add a progress bar.
I hope it will help you!

feedback and improve tips

for ages i searched for an application to also rename and delete files on phone using remote pc. thank you!
still some very important features are missing which hurt the use of application like sorting on files, better gui, different views like thumbnails or lists and viewing folders which have special characters like underline( _ ). i have such a folder and have plenty of content in it but applicatons said that the folder is empty.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.