diff -Nru android-file-transfer-4.2/.github/.editorconfig android-file-transfer-4.3/.github/.editorconfig --- android-file-transfer-4.2/.github/.editorconfig 1970-01-01 00:00:00.000000000 +0000 +++ android-file-transfer-4.3/.github/.editorconfig 2023-12-12 22:22:35.000000000 +0000 @@ -0,0 +1,11 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = false + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 diff -Nru android-file-transfer-4.2/.github/workflows/actions.yml android-file-transfer-4.3/.github/workflows/actions.yml --- android-file-transfer-4.2/.github/workflows/actions.yml 1970-01-01 00:00:00.000000000 +0000 +++ android-file-transfer-4.3/.github/workflows/actions.yml 2023-12-12 22:22:35.000000000 +0000 @@ -0,0 +1,110 @@ +name: Android File Transfer for Linux (and MacOSX!) + +on: [push] + +jobs: + Linux: + runs-on: ubuntu-20.04 + steps: + - name: Creating contiguous... + uses: ncipollo/release-action@v1 + if: github.ref_name == 'master' + with: + name: "The latest and greatest" + body: This is the latest build of the current development branch. Please try any issues using this build before reporting any problems. + token: ${{ secrets.GITHUB_TOKEN }} + tag: continuous + prerelease: true + allowUpdates: true + removeArtifacts: true + - name: Creating release... + uses: ncipollo/release-action@v1 + if: github.ref_type == 'tag' + with: + token: ${{ secrets.GITHUB_TOKEN }} + omitBody: true + omitName: true + allowUpdates: true + removeArtifacts: true + makeLatest: true + - name: Install Dependencies... + run: sudo apt-get -y install qt5-default qttools5-dev qttools5-dev-tools libgtk2.0-dev libfuse-dev libmagic-dev libtag1-dev libssl-dev ninja-build cmake + - name: Checking out sources... + uses: actions/checkout@v2 + - name: Configuring... + run: cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=appdir/usr -B build -GNinja . + - name: Building... + run: ninja -j$(nproc) -C build + - name: Installing... + run: ninja -C build install + - name: Creating AppImage... + run: | + sed -i -e 's|^Name=.*|Name=Android File Transfer For Linux|g' build/appdir/usr/share/applications/android-file-transfer.desktop + wget -c -q "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" + chmod a+x linuxdeployqt-continuous-x86_64.AppImage + unset QTDIR + unset QT_PLUGIN_PATH + unset LD_LIBRARY_PATH + export VERSION=$(git rev-parse --short HEAD) + ./linuxdeployqt-continuous-x86_64.AppImage build/appdir/usr/share/applications/*.desktop -bundle-non-qt-libs -appimage + find build/appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq + - name: Uploading contiguous artifacts... + uses: ncipollo/release-action@v1 + if: github.ref_name == 'master' + with: + allowUpdates: true + omitBody: true + omitName: true + token: ${{ secrets.GITHUB_TOKEN }} + artifacts: ./Android*.AppImage* + tag: continuous + - name: Uploading release artifacts... + uses: ncipollo/release-action@v1 + if: github.ref_type == 'tag' + with: + allowUpdates: true + omitBody: true + omitName: true + token: ${{ secrets.GITHUB_TOKEN }} + artifacts: ./Android*.AppImage* + MacOSX: + runs-on: macos-latest + steps: + - name: Install Dependencies... + run: | + brew tap homebrew/cask + brew install qt5 homebrew/cask/macfuse taglib openssl@1.1 cmake ninja libmagic + - name: Checking out sources... + uses: actions/checkout@v2 + - name: Configuring... + run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1 -DOPENSSL_LIBRARIES=/usr/local/opt/openssl@1.1/lib -DCMAKE_INSTALL_PREFIX=appdir -B build -G Ninja . + - name: Building... + run: ninja -j$(sysctl -n hw.ncpu) -C build + - name: Installing... + run: ninja -C build install + - name: Packaging... + run: | + mv appdir/android-file-transfer.app appdir/Android\ File\ Transfer\ for\ Linux.app + git clone https://github.com/andreyvit/create-dmg.git + cd create-dmg + ./create-dmg --volicon "../osx/android-file-transfer.icns" --icon-size 96 --icon "Android File Transfer for Linux" 110 100 --app-drop-link 380 100 AndroidFileTransferForLinux.dmg ../appdir/Android\ File\ Transfer\ for\ Linux.app + - name: Uploading contiguous artifacts... + uses: ncipollo/release-action@v1 + if: github.ref_name == 'master' + with: + allowUpdates: true + omitBody: true + omitName: true + token: ${{ secrets.GITHUB_TOKEN }} + artifacts: create-dmg/*.dmg + tag: continuous + + - name: Uploading contiguous artifacts... + uses: ncipollo/release-action@v1 + if: github.ref_type == 'tag' + with: + allowUpdates: true + omitBody: true + omitName: true + token: ${{ secrets.GITHUB_TOKEN }} + artifacts: create-dmg/*.dmg diff -Nru android-file-transfer-4.2/.travis.yml android-file-transfer-4.3/.travis.yml --- android-file-transfer-4.2/.travis.yml 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/.travis.yml 2023-12-12 22:22:35.000000000 +0000 @@ -20,7 +20,9 @@ elif [ "${TRAVIS_OS_NAME}" = "freebsd" ]; then sudo pkg install -y ninja qt5-qmake qt5-buildtools qt5-widgets qt5-linguist fusefs-libs pybind11 taglib elif [ "${TRAVIS_OS_NAME}" = "osx" ]; then - brew tap homebrew/cask && brew install qt5 homebrew/cask/osxfuse pybind11 taglib openssl@1.1 + export HOMEBREW_NO_AUTO_UPDATE=1 + brew tap homebrew/cask + brew install qt5 homebrew/cask/osxfuse pybind11 taglib openssl@1.1 fi script: diff -Nru android-file-transfer-4.2/CMakeLists.txt android-file-transfer-4.3/CMakeLists.txt --- android-file-transfer-4.2/CMakeLists.txt 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/CMakeLists.txt 2023-12-12 22:22:35.000000000 +0000 @@ -1,10 +1,10 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.5) project(android-file-transfer) set (CMAKE_CXX_STANDARD 11) set(VERSION_MAJOR "4") -set(VERSION_MINOR "2") +set(VERSION_MINOR "3") set(VERSION_PATCH "0") set(VERSION_COUNT 3) set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") @@ -17,6 +17,10 @@ find_program(OTOOL_BIN otool) endif() +if (${CMAKE_SYSTEM_NAME} MATCHES "Haiku") + set(USB_BACKEND_HAIKU TRUE) +endif() + set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) include(FindPkgConfig) @@ -28,10 +32,11 @@ option(BUILD_FUSE "Build fuse mount helper" ON) option(BUILD_MTPZ "Build with MTPZ support" ON) option(BUILD_PYTHON "Build python module" ON) +set(BUILD_PYTHON_VERSION "" CACHE STRING "Force specific python version to build") option(BUILD_TAGLIB "Build with taglib support" ON) if (BUILD_PYTHON) - find_package (Python COMPONENTS Interpreter Development QUIET) + find_package (Python ${BUILD_PYTHON_VERSION} COMPONENTS Interpreter Development QUIET) endif() if (BUILD_SHARED_LIB) @@ -57,7 +62,9 @@ endif() if (PYTHON_FOUND) - set(CMAKE_POSITION_INDEPENDENT_CODE ON) + if (NOT HAIKU) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + endif() find_package(pybind11) if (NOT pybind11_FOUND) set(CMAKE_REQUIRED_INCLUDES ${Python_INCLUDE_DIRS}) @@ -165,6 +172,14 @@ mtp/backend/darwin/usb/Exception.cpp mtp/backend/darwin/usb/Interface.cpp ) +elseif (USB_BACKEND_HAIKU) + add_definitions(-DUSB_BACKEND_HAIKU) + list(APPEND SOURCES + mtp/backend/haiku/usb/Context.cpp + mtp/backend/haiku/usb/Device.cpp + mtp/backend/haiku/usb/DeviceDescriptor.cpp + ) + list(APPEND MTP_LIBRARIES device) else() list(APPEND SOURCES mtp/backend/linux/usb/Endpoint.cpp @@ -230,6 +245,8 @@ target_include_directories(${LIB_NAME} PUBLIC mtp/backend/libusb) elseif (USB_BACKEND_DARWIN) target_include_directories(${LIB_NAME} PUBLIC mtp/backend/darwin) +elseif (USB_BACKEND_HAIKU) + target_include_directories(${LIB_NAME} PUBLIC mtp/backend/haiku) else() target_include_directories(${LIB_NAME} PUBLIC mtp/backend/linux) endif() diff -Nru android-file-transfer-4.2/README.md android-file-transfer-4.3/README.md --- android-file-transfer-4.2/README.md 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/README.md 2023-12-12 22:22:35.000000000 +0000 @@ -1,86 +1,104 @@ -# Android File Transfer For Linux (FreeBSD and Mac OS X!) +# Android File Transfer For Linux (FreeBSD and macOS, too!) -[![License](http://img.shields.io/:license-LGPLv2.1-blue.svg)](https://github.com/whoozle/android-file-transfer-linux/blob/master/LICENSE) -[![Version](http://img.shields.io/:version-4.2-green.svg)](https://github.com/whoozle/android-file-transfer-linux) -[![Build Status](https://travis-ci.org/whoozle/android-file-transfer-linux.svg?branch=master)](https://travis-ci.org/whoozle/android-file-transfer-linux) -[![Android-File-Transfer-Linux](https://snapcraft.io//android-file-transfer-linux/badge.svg)](https://snapcraft.io/android-file-transfer-linux) +[![License](https://img.shields.io/:license-LGPLv2.1-blue.svg)](https://github.com/whoozle/android-file-transfer-linux/blob/master/LICENSE) +[![Version](https://img.shields.io/:version-4.3-green.svg)](https://github.com/whoozle/android-file-transfer-linux) +[![Android File Transfer for Linux (and macOS!)](https://github.com/whoozle/android-file-transfer-linux/actions/workflows/actions.yml/badge.svg)](https://github.com/whoozle/android-file-transfer-linux/actions/workflows/actions.yml) -Android File Transfer for Linux — reliable MTP client with minimalistic UI similar to [Android File Transfer for Mac](https://www.android.com/intl/en_us/filetransfer/). +Android File Transfer for Linux — a reliable [MTP](https://en.wikipedia.org/wiki/Media_Transfer_Protocol) client with minimalistic UI similar to [Android File Transfer](https://www.android.com/intl/en_us/filetransfer/). It just works™. ## Do I need it? -If you're happy with `gmtp`/`gvfs`/`mtpfs` or any other mtp software, you might not need this software (but give it a try!). +If you're happy with `gmtp`/`gvfs`/`mtpfs` or any other MTP software, you might not need this software (but give it a try!). -If you're suffering from crashes, missing tags, album covers, usb freezes and corrupted files, this software is right for you. +If you're suffering from crashes, missing tags, album covers, USB freezes, and corrupted files however, this software is right for you. ## Pre-built Packages -If your distribution does not provide `android-file-transfer-linux` package, you can still install it in your system. +If your distribution does not provide an `android-file-transfer-linux` package, you can still install it on your system. + There's quite a few packages available: -- Snapcraft: https://snapcraft.io/android-file-transfer-linux - AppImage: https://github.com/whoozle/android-file-transfer-linux/releases -- MacOSX DMG image: https://github.com/whoozle/android-file-transfer-linux/releases -- MacOSX Homebrew: `brew cask install whoozle-android-file-transfer` or `brew cask install whoozle-android-file-transfer-nightly` +- macOS DMG image: https://github.com/whoozle/android-file-transfer-linux/releases +- macOS Homebrew: `brew cask install whoozle-android-file-transfer` or `brew cask install whoozle-android-file-transfer-nightly` ## Support me -If you want to help me with development, click on the link below and follow the instructions. I'm developing AFTL in my spare time and try to fix everything as fast as possible, sometimes adding features in realtime (more than 100 tickes closed by now). -Any amount would help relieving pain of using MTP. :D +If you want to help me with development, click on the link below and follow the instructions. I'm working on this project in my spare time and I try to fix everything as fast as possible, sometimes adding features in realtime (more than 100 tickets closed by now). +Any amount would help relieving the pain of using MTP. :D https://www.paypal.me/whoozle ## Features * Simple Qt UI with progress dialogs. -* FUSE wrapper (If you'd prefer mounting your device), supporting partial read/writes, allowing instant access to your files. +* FUSE wrapper (if you prefer mounting your device), supporting partial read/writes, allowing instant access to your files. * No file size limits. * Automatically renames album cover to make it visible from media player. -* Supports Zune/Zune HD. -* USB 'Zerocopy' support found in recent Linux kernel (no user/kernel data copying) -* No extra dependencies (e.g. `libptp`/`libmtp`). -* Available as static/shared library. -* Command line tool (aft-mtp-cli) +* Supports Zune and Zune HD. +* USB [zerocopy](https://docs.kernel.org/networking/msg_zerocopy.html) support found in recent Linux kernels (no user/kernel data copying). +* No extra dependencies (e.g. `libptp` or `libmtp`). +* Available as a static/shared library. +* Command line tool [`aft-mtp-cli`](https://manpages.debian.org/testing/android-file-transfer/aft-mtp-cli.1.en.html). +* Python bindings. ## FAQ -[Please take a look at FAQ if you have issues with your operating system](FAQ.md). It's not that big, but those are the questions asked very often. +[Please take a look at the FAQ if you have issues with your operating system](FAQ.md). It's not that big, but those are the questions asked very often. + +## Installation + +### Debian/Ubuntu -## Building instructions +``` +sudo apt-get install android-file-transfer +``` ### Gentoo - AFT for Linux is now included in Gentoo, you don't have to build anything, just run - ``` - sudo emerge -av sys-fs/android-file-transfer-linux - ``` +Android File Transfer for Linux is now included in Gentoo. You don't have to build anything, just run - If you need fuse mount helper to mount MTP filesystem, you have to enable fuse use flag, e.g. adding the following in /etc/portage/package.use (which can either be a directory or a file) - ``` - sys-fs/android-file-transfer-linux fuse - ``` +``` +sudo emerge -av sys-fs/android-file-transfer-linux +``` + +If you need a FUSE mount helper to mount MTP filesystems, you have to enable the FUSE use flag, e.g. adding the following in `/etc/portage/package.use` (which can either be a directory or a file): +``` +sys-fs/android-file-transfer-linux fuse +``` + +You can use the `sys-fs/android-file-transfer-linux-9999` ebuild if you want the latest Git version by adding the following entry to `/etc/portage/package.accept_keywords (which can either be a directory or a file): +``` +=sys-fs/android-file-transfer-linux-9999 ** +``` + +### Arch - You can use ```sys-fs/android-file-transfer-linux-9999``` ebuild if you want the latest git-version by adding the following entry into /etc/portage/package.accept_keywords (which can either be a directory or a file) ``` - =sys-fs/android-file-transfer-linux-9999 ** + sudo pacman -S android-file-transfer ``` +## Building from source + ### Prerequisites -* You will need Qt libraries for building ui program. If you're planning to use only library (*Qt is not needed*), you could turn the option ```BUILD_QT_UI``` off. -* For ubuntu and other debian-based distros use the following command: +* You will need the Qt libraries for building the UI program. If you're planning to use only the library (*Qt is not needed*), you could turn the option ```BUILD_QT_UI``` off. +* For Ubuntu and other Debian-based distros, use the following command: ```shell - sudo apt-get install build-essential cmake qt5-default ninja-build libfuse-dev libreadline-dev + sudo apt-get install build-essential cmake qt5-default ninja-build libfuse-dev libreadline-dev qttools5-dev ``` For Fedora: ``` - dnf install make automake gcc gcc-c++ kernel-devel cmake fuse fuse-devel qt-devel readline-devel + dnf install make automake gcc gcc-c++ kernel-devel cmake fuse fuse-devel qt-devel readline-devel libqt5-linguist-devel ``` -* Basically, you need `libqtX-dev` for UI, `libfuse-dev` for FUSE interface, `cmake`, `ninja` or `make` for building the project. You could use libqt5-dev as well. +* Basically + * you need `libqtX-dev` or `libqt5-dev` for the UI, + * `libfuse-dev` for the FUSE interface, + * and `cmake`, `ninja`, or `make` for building the project. -### Building with ninja +### Building with Ninja ```shell mkdir build @@ -102,25 +120,25 @@ ./qt/android-file-transfer ``` -### Installing binary package on OS X / macOS +### Installing binary package on macOS There is a binary package that can be installed via Homebrew: - * First [install brew](https://brew.sh) if you don't have it already installed. - * Then the stable package may be installed via: + * First, install [`brew`](https://brew.sh) if you don't have it already installed. + * Then, the stable package may be installed via: ```shell brew install homebrew/cask/whoozle-android-file-transfer ``` - * Nighlty build may be installed via; + * The nightly build may be installed via: ```shell brew install homebrew/cask-versions/whoozle-android-file-transfer-nightly ``` - * Please note: they are in conflict, so please make sure to uninstall it when you want switch between stable and nightly. + * Please note: Stable and nightly are in conflict, so please make sure to uninstall one of them when you want to switch between stable and nightly. -### Building app package on OS X / macOS +### Building app package on macOS -You'll need Qt installed to build the GUI app. Here are the build instructions with qt5 from homebrew (`brew install qt5`): +You'll need Qt installed to build the GUI app. Here are the build instructions with Qt5 from Homebrew (`brew install qt5`): ```shell mkdir build @@ -134,7 +152,7 @@ ### Installation -`sudo ninja install` or `sudo make install` will install program into cmake prefix/bin directory (usually /usr/local/bin) +`sudo ninja install` or `sudo make install` will install the program into the cmake prefix/bin directory (usually `/usr/local/bin`). ## How to use @@ -146,35 +164,32 @@ ./aft-mtp-mount ~/my-device ``` Remember, if you want album art to be displayed, it must be named 'albumart.xxx' and placed *first* in the destination folder. Then copy other files. -Also, note that fuse could be 7-8 times slower than ui/cli file transfer. +Also, note that FUSE could be 7-8 times slower than UI/CLI file transfer. ### Qt user interface 1. Start application, choose destination folder and click any button on toolbar. - -2. The options available there are: `Upload Album`, `Upload Directory` and `Upload Files`. - The latter two are self-explanatory. `Upload album` tries searching source directory for album cover and sets best available cover. - -3. You could drop any files or folders right into application window, the transfer will start automatically. +2. The options available are: `Upload Album`, `Upload Directory`, and `Upload Files`. + The latter two are self-explanatory. `Upload Album` tries searching the source directory for album covers and sets the best available cover. +3. You could drop any files or folders right into the application window: the transfer will start automatically. ### Known problems +* Samsung removed Android extensions from MTP, so FUSE will be available read-only, sorry. Feel free to post your complaints to https://forum.developer.samsung.com/ +* Sometimes downloading fails with a USB timeout, after which the phone becomes unresponsive: [Android bug #75259](https://code.google.com/p/android/issues/detail?id=75259) +* Objects created in the UI will not show up in the FUSE filesystem: [Android bug #169547](https://code.google.com/p/android/issues/detail?id=169547) -* Samsung removed android extensions from MTP, so fuse will be available readonly, sorry. Feel free to post your complaints to http://developer.samsung.com/forum/en -* Sometimes downloading fails with usb timeout, then phone becomes unresponsive. [Android bug #75259](https://code.google.com/p/android/issues/detail?id=75259) -* Objects created in UI will not show up in FUSE filesystem. [Android bug #169547](https://code.google.com/p/android/issues/detail?id=169547) - -Up to date list of all known problems and bugs available [here](https://github.com/whoozle/android-file-transfer-linux/issues) +Up-to-date list of all known problems and bugs are available [here](https://github.com/whoozle/android-file-transfer-linux/issues). ## Contacts -Please do not hesitate to contact me if you have any further questions, my email address is . +Please do not hesitate to contact me if you have any further questions. My email address is . ## Special thanks -* All who filed bugs on github and wrote emails, many features appeared only because of your feedback. Thanks! -* Alexey [gazay](https://github.com/gazay) Gaziev for useful suggestions, support and invaluable help with MacBook and MacOSX port. +* All who filed bugs on GitHub and wrote emails. Many features came to be only because of your feedback. Thanks! +* Alexey [gazay](https://github.com/gazay) Gaziev for useful suggestions, support, and invaluable help with the MacBook and macOS port. * @ssnjrthegr8 for the new logo! ## License -Android File Transfer for Linux is released under [GNU LGPLv2.1 License](https://github.com/whoozle/android-file-transfer-linux/blob/master/LICENSE). +Android File Transfer for Linux is released under the [GNU LGPLv2.1 License](https://github.com/whoozle/android-file-transfer-linux/blob/master/LICENSE). -Copyright © 2015-2020 Vladimir Menshakov +Copyright © 2015-2022 Vladimir Menshakov diff -Nru android-file-transfer-4.2/android-file-transfer-linux.json android-file-transfer-4.3/android-file-transfer-linux.json --- android-file-transfer-4.2/android-file-transfer-linux.json 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/android-file-transfer-linux.json 2023-12-12 22:22:35.000000000 +0000 @@ -13,7 +13,7 @@ "config-opts": [ "-DCMAKE_BUILD_TYPE=Release" ], "sources": [{ "type": "git", - "tag": "v4.2", + "tag": "v4.3", "url": "https://github.com/whoozle/android-file-transfer-linux.git" }] } diff -Nru android-file-transfer-4.2/cli/CMakeLists.txt android-file-transfer-4.3/cli/CMakeLists.txt --- android-file-transfer-4.2/cli/CMakeLists.txt 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/cli/CMakeLists.txt 2023-12-12 22:22:35.000000000 +0000 @@ -13,7 +13,7 @@ set(CMAKE_REQUIRED_LIBRARIES ${Readline_LIBRARY}) set(READLINE_TEST_SRC " #include -#include +#include int main(int argc, char **argv) { char *line = readline(\">\"); diff -Nru android-file-transfer-4.2/cli/CommandLine.cpp android-file-transfer-4.3/cli/CommandLine.cpp --- android-file-transfer-4.2/cli/CommandLine.cpp 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/cli/CommandLine.cpp 2023-12-12 22:22:35.000000000 +0000 @@ -23,8 +23,10 @@ #include #include #include -#include -#include +#include +#if HAVE_READLINE_HISTORY_H +#include +#endif namespace cli { diff -Nru android-file-transfer-4.2/cli/cli.cpp android-file-transfer-4.3/cli/cli.cpp --- android-file-transfer-4.2/cli/cli.cpp 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/cli/cli.cpp 2023-12-12 22:22:35.000000000 +0000 @@ -91,7 +91,7 @@ {"input-file", required_argument, 0, 'f' }, {"reset-device", no_argument , 0, 'R' }, {"device-name", required_argument, 0, 'd' }, - {"device-list", required_argument, 0, 'l' }, + {"device-list", no_argument, 0, 'l' }, {0, 0, 0, 0 } }; diff -Nru android-file-transfer-4.2/cmake/FindReadline.cmake android-file-transfer-4.3/cmake/FindReadline.cmake --- android-file-transfer-4.2/cmake/FindReadline.cmake 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/cmake/FindReadline.cmake 2023-12-12 22:22:35.000000000 +0000 @@ -18,27 +18,37 @@ # Readline_LIBRARY The readline library. find_path(Readline_ROOT_DIR - NAMES include/readline/readline.h + NAMES include/readline/readline.h includes/editline/readline.h + develop/headers/x86/readline/readline.h develop/headers/x86/editline/readline.h + develop/headers/readline/readline.h develop/headers/editline/readline.h ) find_path(Readline_INCLUDE_DIR - NAMES readline/readline.h - HINTS ${Readline_ROOT_DIR}/include + NAMES readline.h + HINTS ${Readline_ROOT_DIR}/include/readline ${Readline_ROOT_DIR}/develop/headers/x86/readline + ${Readline_ROOT_DIR}/develop/headers/readline + ${Readline_ROOT_DIR}/include/editline ${Readline_ROOT_DIR}/develop/headers/x86/editline + ${Readline_ROOT_DIR}/develop/headers/editline ) +if(EXISTS ${Readline_INCLUDE_DIR}/history.h) + add_definitions(-DHAVE_READLINE_HISTORY_H=1) +endif() + find_library(Readline_LIBRARY - NAMES readline - HINTS ${Readline_ROOT_DIR}/lib + NAMES readline edit + HINTS ${Readline_ROOT_DIR}/lib ${Readline_ROOT_DIR}/develop/lib/x86 + ${Readline_ROOT_DIR}/develop/lib ) -if(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) +if(Readline_INCLUDE_DIR AND Readline_LIBRARY) set(READLINE_FOUND TRUE) -else(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) - FIND_LIBRARY(Readline_LIBRARY NAMES readline) +else(Readline_INCLUDE_DIR AND Readline_LIBRARY) + FIND_LIBRARY(Readline_LIBRARY NAMES readline edit) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG Readline_INCLUDE_DIR Readline_LIBRARY ) MARK_AS_ADVANCED(Readline_INCLUDE_DIR Readline_LIBRARY) -endif(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) +endif(Readline_INCLUDE_DIR AND Readline_LIBRARY) mark_as_advanced( Readline_ROOT_DIR diff -Nru android-file-transfer-4.2/debian/changelog android-file-transfer-4.3/debian/changelog --- android-file-transfer-4.2/debian/changelog 2023-07-25 09:44:39.000000000 +0000 +++ android-file-transfer-4.3/debian/changelog 2024-01-16 08:50:15.000000000 +0000 @@ -1,3 +1,12 @@ +android-file-transfer (4.3-1) unstable; urgency=medium + + * New upstream version 4.3 + * Drop patch included in upstream release + * Do not repack upstream tarball, no longer needed + * Standards-Version: 4.6.2 (no changes required) + + -- Dylan Aïssi Tue, 16 Jan 2024 09:50:15 +0100 + android-file-transfer (4.2-2) unstable; urgency=medium * Cherry-pick upstream fix for GCC 13 (Closes: 1037572) diff -Nru android-file-transfer-4.2/debian/control android-file-transfer-4.3/debian/control --- android-file-transfer-4.2/debian/control 2023-07-25 09:44:39.000000000 +0000 +++ android-file-transfer-4.3/debian/control 2024-01-16 08:50:15.000000000 +0000 @@ -9,7 +9,7 @@ libreadline-dev, pkg-config, qttools5-dev -Standards-Version: 4.5.1 +Standards-Version: 4.6.2 Vcs-Browser: https://salsa.debian.org/debian/android-file-transfer Vcs-Git: https://salsa.debian.org/debian/android-file-transfer.git Homepage: https://whoozle.github.io/android-file-transfer-linux/ diff -Nru android-file-transfer-4.2/debian/copyright android-file-transfer-4.3/debian/copyright --- android-file-transfer-4.2/debian/copyright 2023-07-25 09:44:39.000000000 +0000 +++ android-file-transfer-4.3/debian/copyright 2024-01-16 08:50:15.000000000 +0000 @@ -2,7 +2,6 @@ Upstream-Name: android-file-transfer Upstream-Contact: Vladimir Menshakov Source: https://github.com/whoozle/android-file-transfer-linux -Files-Excluded: osx/ds_store_template Files: * Copyright: 2015-2019, Vladimir Menshakov diff -Nru android-file-transfer-4.2/debian/patches/Fix_GCC_13.patch android-file-transfer-4.3/debian/patches/Fix_GCC_13.patch --- android-file-transfer-4.2/debian/patches/Fix_GCC_13.patch 2023-07-25 09:44:39.000000000 +0000 +++ android-file-transfer-4.3/debian/patches/Fix_GCC_13.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -From e8f45ff6f02d2e1e09c12f3aa708e87548d4f2bd Mon Sep 17 00:00:00 2001 -From: Sam James -Date: Tue, 18 Apr 2023 11:38:22 +0100 -Subject: [PATCH] Fix build with GCC 13 (#330) - -GCC 13 (as usual for new compiler releases) shuffles around some internal includes so some -are no longer transitively included. - -See https://gnu.org/software/gcc/gcc-13/porting_to.html. - -Origin: https://github.com/whoozle/android-file-transfer-linux/commit/e8f45ff6f02d2e1e09c12f3aa708e87548d4f2bd -Bug-Gentoo: https://bugs.gentoo.org/894788 -Bug-Debian: https://bugs.debian.org/1037572 ---- - mtp/types.h | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/mtp/types.h b/mtp/types.h -index 32024e4e..676e180e 100644 ---- a/mtp/types.h -+++ b/mtp/types.h -@@ -27,6 +27,7 @@ - #include - #include - #include -+#include - #include - - namespace mtp diff -Nru android-file-transfer-4.2/debian/patches/series android-file-transfer-4.3/debian/patches/series --- android-file-transfer-4.2/debian/patches/series 2023-07-25 09:44:39.000000000 +0000 +++ android-file-transfer-4.3/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -Fix_GCC_13.patch diff -Nru android-file-transfer-4.2/debian/watch android-file-transfer-4.3/debian/watch --- android-file-transfer-4.2/debian/watch 2023-07-25 09:44:39.000000000 +0000 +++ android-file-transfer-4.3/debian/watch 2024-01-16 08:50:15.000000000 +0000 @@ -1,4 +1,3 @@ version=4 -opts=repacksuffix=+dfsg,dversionmangle=s/\+dfsg//g,repack,compression=xz,\ - filenamemangle=s/.+\/v?(\d\S+)\.tar\.gz/android-file-transfer-linux-$1\.tar\.gz/ \ +opts=filenamemangle=s/.+\/v?(\d\S+)\.tar\.gz/android-file-transfer-linux-$1\.tar\.gz/ \ https://github.com/whoozle/android-file-transfer-linux/tags .*/v?(\d\S+)\.tar\.gz diff -Nru android-file-transfer-4.2/fuse/CMakeLists.txt android-file-transfer-4.3/fuse/CMakeLists.txt --- android-file-transfer-4.2/fuse/CMakeLists.txt 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/fuse/CMakeLists.txt 2023-12-12 22:22:35.000000000 +0000 @@ -1,4 +1,4 @@ -add_executable(aft-mtp-mount fuse_ll.cpp) +add_executable(aft-mtp-mount fuse.cpp) target_link_libraries(aft-mtp-mount ${MTP_LIBRARIES} ${FUSE_LDFLAGS} ${CMAKE_THREAD_LIBS_INIT}) install(TARGETS aft-mtp-mount RUNTIME DESTINATION bin) diff -Nru android-file-transfer-4.2/fuse/fuse.cpp android-file-transfer-4.3/fuse/fuse.cpp --- android-file-transfer-4.2/fuse/fuse.cpp 1970-01-01 00:00:00.000000000 +0000 +++ android-file-transfer-4.3/fuse/fuse.cpp 2023-12-12 22:22:35.000000000 +0000 @@ -0,0 +1,991 @@ +/* + This file is part of Android File Transfer For Linux. + Copyright (C) 2015-2020 Vladimir Menshakov + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ + using namespace mtp::fuse; + + class FuseWrapper + { + std::mutex _mutex; + std::string _deviceFilter; + bool _claimInterface; + bool _resetDevice; + mtp::DevicePtr _device; + mtp::SessionPtr _session; + bool _editObjectSupported; + bool _getObjectPropertyListSupported; + bool _getPartialObjectSupported; + time_t _connectTime; + size_t _partialObjectCacheSize; + + static constexpr size_t DefaultPartialObjectCacheSize = 3; //just 3 object entries. + + using ChildrenObjects = std::map; + using Files = std::map; + Files _files; + + typedef std::map ObjectAttrs; + ObjectAttrs _objectAttrs; + + typedef mtp::Session::ObjectEditSessionPtr ObjectEditSessionPtr; + typedef std::map OpenedFiles; + OpenedFiles _openedFiles; + + typedef std::map DirectoryCache; + DirectoryCache _directoryCache; + + static const size_t MtpStorageShift = FUSE_ROOT_ID + 1; + static const size_t MtpObjectShift = 999998 + MtpStorageShift; + + std::vector _storageIdList; + std::map _storageToName; + std::map _storageFromName; + + struct ObjectCacheEntry + { + mtp::ObjectId Id; + mtp::ByteArray Data; + + ObjectCacheEntry() + { } + + ObjectCacheEntry(mtp::ObjectId id, mtp::ByteArray && data): Id(id), Data(std::move(data)) + { } + }; + + using ObjectCache = std::list; + ObjectCache _objectCache; + + private: + static FuseId ToFuse(mtp::ObjectId id) + { return FuseId(id.Id + MtpObjectShift); } + + static mtp::ObjectId FromFuse(FuseId id) + { return mtp::ObjectId(id.Inode - MtpObjectShift); } + + static bool IsStorage(FuseId id) + { return id.Inode >= MtpStorageShift && id.Inode <= MtpObjectShift; } + + mtp::StorageId FuseIdToStorageId(FuseId id) const + { + if (!IsStorage(id)) + throw std::runtime_error("converting non-storage inode to storage id"); + size_t i = id.Inode - MtpStorageShift; + if (i >= _storageIdList.size()) + throw std::runtime_error("invalid storage id"); + return _storageIdList[i]; + } + + FuseId FuseIdFromStorageId(mtp::StorageId storageId) const + { + auto i = std::find(_storageIdList.begin(), _storageIdList.end(), storageId); + if (i == _storageIdList.end()) + throw std::runtime_error("invalid storage id"); + return FuseId(MtpStorageShift + std::distance(_storageIdList.begin(), i)); + } + + void GetObjectInfo(ChildrenObjects &cache, mtp::ObjectId id) + { + auto oi = _session->GetObjectInfo(id); + + FuseId inode = ToFuse(id); + cache.emplace(oi.Filename, inode); + + struct stat &attr = _objectAttrs[id]; + attr.st_uid = getuid(); + attr.st_gid = getgid(); + attr.st_ino = inode.Inode; + attr.st_mode = FuseEntry::GetMode(oi.ObjectFormat); + attr.st_atime = attr.st_mtime = mtp::ConvertDateTime(oi.ModificationDate); + attr.st_ctime = mtp::ConvertDateTime(oi.CaptureDate); + attr.st_size = oi.ObjectCompressedSize != mtp::MaxObjectSize? oi.ObjectCompressedSize: _session->GetObjectIntegerProperty(id, mtp::ObjectProperty::ObjectSize); + attr.st_nlink = 1; + } + + struct stat GetObjectAttr(FuseId inode) + { + if (inode == FuseId::Root) + { + struct stat attr = { }; + attr.st_uid = getuid(); + attr.st_gid = getgid(); + attr.st_ino = inode.Inode; + attr.st_mtime = attr.st_ctime = attr.st_atime = _connectTime; + attr.st_mode = FuseEntry::DirectoryMode; + attr.st_nlink = 1; + return attr; + } + + if (IsStorage(inode)) + { + struct stat attr = { }; + attr.st_uid = getuid(); + attr.st_gid = getgid(); + attr.st_ino = inode.Inode; + attr.st_mtime = attr.st_ctime = attr.st_atime = _connectTime; + attr.st_mode = FuseEntry::DirectoryMode; + attr.st_nlink = 1; + return attr; + } + + mtp::ObjectId id = FromFuse(inode); + auto i = _objectAttrs.find(id); + if (i != _objectAttrs.end()) + return i->second; + + //populate cache for parent + auto parent = GetParentObject(inode); + GetChildren(parent); //populate cache + + i = _objectAttrs.find(id); + if (i != _objectAttrs.end()) + return i->second; + else + throw std::runtime_error("no such object"); + } + + template + void GetObjectPropertyList(mtp::ObjectId parent, const std::set &originalObjectList, const mtp::ObjectProperty property, + const std::function &callback) + { + std::set objectList(originalObjectList); + mtp::ByteArray data = _session->GetObjectPropertyList(parent, mtp::ObjectFormat::Any, property, 0, 1); + mtp::ObjectPropertyListParser parser; + + parser.Parse(data, [&objectList, &callback, property](mtp::ObjectId objectId, mtp::ObjectProperty p, const PropertyValueType & value) { + auto it = objectList.find(objectId); + if (property == p && it != objectList.end()) + { + objectList.erase(it); + try { callback(objectId, value); } catch(const std::exception &ex) { mtp::error("callback for property list 0x", mtp::hex(property, 4), " failed: ", ex.what()); } + } + else + mtp::debug("extra property 0x", hex(p, 4), " returned for object ", objectId.Id, ", while querying property list 0x", mtp::hex(property, 4)); + }); + + if (!objectList.empty()) + { + mtp::error("inconsistent GetObjectPropertyList for property 0x", mtp::hex(property, 4)); + for(auto objectId : objectList) + { + mtp::debug("querying property 0x", mtp::hex(property, 4), " for object ", objectId); + try + { + mtp::ByteArray data = _session->GetObjectProperty(objectId, property); + mtp::InputStream stream(data); + PropertyValueType value = PropertyValueType(); + stream >> value; + callback(objectId, value); + } + catch(const std::exception &ex) { mtp::error("fallback query/callback for property 0x", mtp::hex(property, 4), " failed: ", ex.what()); } + } + } + } + + ChildrenObjects & GetChildren(FuseId inode) + { + if (inode == FuseId::Root) + { + PopulateStorages(); + ChildrenObjects & cache = _files[inode]; + cache.clear(); + for(size_t i = 0; i < _storageIdList.size(); ++i) + { + mtp::StorageId storageId = _storageIdList[i]; + auto name = _storageToName.find(storageId); + if (name != _storageToName.end()) + cache.emplace(name->second, FuseId(MtpStorageShift + i)); + else + mtp::error("no storage name for ", storageId); + } + return cache; + } + + { + auto i = _files.find(inode); + if (i != _files.end()) + return i->second; + } + + ChildrenObjects & cache = _files[inode]; + + using namespace mtp; + if (inode == FuseId::Root) + { + return cache; + } + + msg::ObjectHandles oh; + + if (IsStorage(inode)) + { + mtp::StorageId storageId = FuseIdToStorageId(inode); + oh = _session->GetObjectHandles(storageId, mtp::ObjectFormat::Any, mtp::Session::Root); + } + else + { + mtp::ObjectId parent = FromFuse(inode); + oh = _session->GetObjectHandles(mtp::Session::AllStorages, mtp::ObjectFormat::Any, parent); + + if (_getObjectPropertyListSupported) + { + std::set objects; + for(auto id : oh.ObjectHandles) + objects.insert(id); + + //populate filenames + GetObjectPropertyList(parent, objects, mtp::ObjectProperty::ObjectFilename, + [&cache](ObjectId objectId, const std::string &name) + { cache.emplace(name, ToFuse(objectId)); }); + + //format + GetObjectPropertyList(parent, objects, mtp::ObjectProperty::ObjectFormat, + [this](ObjectId objectId, mtp::ObjectFormat format) + { + struct stat & attr = _objectAttrs[objectId]; + attr.st_ino = ToFuse(objectId).Inode; + attr.st_mode = FuseEntry::GetMode(format); + attr.st_nlink = 1; + }); + + //size + GetObjectPropertyList(parent, objects, mtp::ObjectProperty::ObjectSize, + [this](ObjectId objectId, mtp::u64 size) + { _objectAttrs[objectId].st_size = size; }); + + //mtime + try + { + GetObjectPropertyList(parent, objects, mtp::ObjectProperty::DateModified, + [this](ObjectId objectId, const std::string & mtime) + { _objectAttrs[objectId].st_mtime = mtp::ConvertDateTime(mtime); }); + } + catch(const std::exception &ex) + { } + + //ctime + try + { + GetObjectPropertyList(parent, objects, mtp::ObjectProperty::DateAdded, + [this](ObjectId objectId, const std::string & ctime) + { _objectAttrs[objectId].st_ctime = mtp::ConvertDateTime(ctime); }); + } + catch(const std::exception &ex) + { } + + return cache; + } + } + + for(auto id : oh.ObjectHandles) + { + try + { + GetObjectInfo(cache, id); + } catch(const std::exception &ex) + { } + } + return cache; + } + + FuseId CreateObject(FuseId parentInode, const std::string &filename, mtp::ObjectFormat format) + { + mtp::ObjectId parentId = FromFuse(parentInode); + mtp::StorageId storageId; + if (IsStorage(parentInode)) + { + storageId = FuseIdToStorageId(parentInode); + parentId = mtp::Session::Root; + } + else + storageId = _session->GetObjectStorage(parentId); + mtp::debug(" creating object in storage ", mtp::hex(storageId.Id), ", parent: ", mtp::hex(parentId.Id, 8)); + + mtp::msg::NewObjectInfo noi; + if (format != mtp::ObjectFormat::Association) + { + mtp::msg::ObjectInfo oi; + oi.Filename = filename; + oi.ObjectFormat = format; + noi = _session->SendObjectInfo(oi, storageId, parentId); + _session->SendObject(std::make_shared(mtp::ByteArray())); + } + else + noi = _session->CreateDirectory(filename, parentId, storageId); + + mtp::debug(" new object id ", noi.ObjectId.Id); + + { //update cache: + auto i = _files.find(parentInode); + if (i != _files.end()) + GetObjectInfo(i->second, noi.ObjectId); //insert object info into cache + _directoryCache.erase(parentInode); + } + return ToFuse(noi.ObjectId); + } + + void CreateObject(mtp::ObjectFormat format, fuse_req_t req, FuseId parentId, const char *name, mode_t mode, fuse_file_info *createInfo = NULL) + { + if (parentId == FuseId::Root) + { + FUSE_CALL(fuse_reply_err(req, EPERM)); //cannot create files in the same level with storages + return; + } + auto objectId = CreateObject(parentId, name, format); + FuseEntry entry(req); + entry.SetId(objectId); + entry.attr = GetObjectAttr(objectId); + + if (createInfo) + entry.ReplyCreate(createInfo); + else + entry.Reply(); + } + + FuseId GetParentObject(FuseId inode) + { + if (inode == FuseId::Root) + return inode; + + if (IsStorage(inode)) + return FuseId::Root; + + mtp::ObjectId id = FromFuse(inode); + mtp::ObjectId parent = _session->GetObjectParent(id); + if (parent == mtp::Session::Device || parent == mtp::Session::Root) //parent == root -> storage + { + return FuseIdFromStorageId(_session->GetObjectStorage(id)); + } + else + return ToFuse(parent); + } + + bool FillEntry(FuseEntry &entry, FuseId id) + { + try { entry.attr = GetObjectAttr(id); } catch(const std::exception &ex) { return false; } + entry.SetId(id); + return true; + } + + public: + FuseWrapper(const std::string & deviceFilter, bool claimInterface, bool resetDevice): + _deviceFilter(deviceFilter), _claimInterface(claimInterface), _resetDevice(resetDevice), _partialObjectCacheSize(DefaultPartialObjectCacheSize) + { Connect(); } + + void Connect() + { + mtp::scoped_mutex_lock l(_mutex); + + _openedFiles.clear(); + _files.clear(); + _objectAttrs.clear(); + _directoryCache.clear(); + _session.reset(); + _device.reset(); + _device = mtp::Device::FindFirst(_deviceFilter, _claimInterface, _resetDevice); + if (!_device) + throw std::runtime_error("no MTP device found"); + + _session = _device->OpenSession(1); + _editObjectSupported = _session->EditObjectSupported(); + if (!_editObjectSupported) + mtp::error("your device does not have android EditObject extension, you will not be able to write into individual files"); + _getObjectPropertyListSupported = _session->GetObjectPropertyListSupported(); + if (!_getObjectPropertyListSupported) + mtp::error("your device does not have GetObjectPropertyList extension, expect slow enumeration of big directories"); + _getPartialObjectSupported = _session->GetDeviceInfo().Supports(mtp::OperationCode::GetPartialObject); + if (!_getPartialObjectSupported) + mtp::error("your device does not have GetPartialObject extension (beware, this will download the latest N objects you accessed and keep them in a small in-memory cache)"); + + _connectTime = time(NULL); + PopulateStorages(); + } + + void PopulateStorages() + { + _storageIdList.clear(); + _storageFromName.clear(); + _storageToName.clear(); + mtp::msg::StorageIDs ids = _session->GetStorageIDs(); + _storageIdList.reserve(ids.StorageIDs.size()); + for(size_t i = 0; i < ids.StorageIDs.size(); ++i) + { + mtp::StorageId id = ids.StorageIDs[i]; + mtp::msg::StorageInfo si = _session->GetStorageInfo(id); + std::string path = si.GetName(); + if (path.empty()) + { + char buf[64]; + std::snprintf(buf, sizeof(buf), "sdcard%u", (unsigned)i); + path = buf; + } + FuseId inode(MtpStorageShift + i); + _storageIdList.push_back(id); + _storageFromName[path] = id; + _storageToName[id] = path; + } + } + std::string GetMountpoint() + { return _session->GetDeviceInfo().GetFilesystemFriendlyName(); } + + void Init(void *, fuse_conn_info *conn) + { + mtp::scoped_mutex_lock l(_mutex); + conn->want |= conn->capable & FUSE_CAP_BIG_WRITES; + } + + void Lookup (fuse_req_t req, FuseId parent, const char *name) + { + mtp::scoped_mutex_lock l(_mutex); + FuseEntry entry(req); + + const ChildrenObjects & children = GetChildren(parent); + auto it = children.find(name); + if (it != children.end()) + { + if (FillEntry(entry, it->second)) + { + entry.Reply(); + return; + } + } + entry.ReplyError(ENOENT); + } + + void ReadDir(fuse_req_t req, FuseId ino, size_t size, off_t off, struct fuse_file_info *fi) + { + mtp::scoped_mutex_lock l(_mutex); + if (!(GetObjectAttr(ino).st_mode & S_IFDIR)) + { + FUSE_CALL(fuse_reply_err(req, ENOTDIR)); + return; + } + + FuseDirectory dir(req); + auto it = _directoryCache.find(ino); + if (it == _directoryCache.end()) + { + const ChildrenObjects & cache = GetChildren(ino); + + it = _directoryCache.insert(std::make_pair(ino, CharArray())).first; + CharArray &data = it->second; + + dir.Add(data, ".", GetObjectAttr(FuseId::Root)); + dir.Add(data, "..", GetObjectAttr(GetParentObject(ino))); + for(auto entry : cache) + { + dir.Add(data, entry.first, GetObjectAttr(entry.second)); + } + } + + dir.Reply(req, it->second, off, size); + } + + void GetAttr(fuse_req_t req, FuseId ino, struct fuse_file_info *fi) + { + mtp::scoped_mutex_lock l(_mutex); + FuseEntry entry(req); + if (FillEntry(entry, ino)) + entry.ReplyAttr(); + else + entry.ReplyError(ENOENT); + } + + mtp::Session::ObjectEditSessionPtr GetTransaction(FuseId inode) + { + mtp::Session::ObjectEditSessionPtr tr; + { + auto it = _openedFiles.find(inode); + if (it != _openedFiles.end()) + tr = it->second; + else + { + tr = mtp::Session::EditObject(_session, FromFuse(inode)); + _openedFiles[inode] = tr; + } + } + return NOT_NULL(tr); + } + + void Read(fuse_req_t req, FuseId ino, size_t size, off_t begin, struct fuse_file_info *fi) + { + mtp::scoped_mutex_lock l(_mutex); + ReleaseTransaction(ino); + struct stat attr = GetObjectAttr(ino); + off_t rsize = std::min(attr.st_size - begin, size); + mtp::debug("reading ", rsize, " bytes"); + mtp::ByteArray data; + auto objectId = FromFuse(ino); + if (rsize > 0) + { + if (!_getPartialObjectSupported) + { + std::size_t size = 0; + + ObjectCache::iterator it; + for(it = _objectCache.begin(); it != _objectCache.end(); ++it, ++size) + { + auto & entry = *it; + if (entry.Id == objectId) + { + mtp::debug("in-memory cache hit"); + auto src = entry.Data.data() + begin; + std::copy(src, src + rsize, std::back_inserter(data)); + if (it != _objectCache.begin()) + std::swap(*_objectCache.begin(), *it); + break; + } + } + if (it == _objectCache.end()) + { + mtp::debug("in-memory cache miss"); + auto stream = std::make_shared(); + _session->GetObject(objectId, stream); + auto src = stream->GetData().data() + begin; + std::copy(src, src + rsize, std::back_inserter(data)); + _objectCache.emplace_front(objectId, std::move(stream->GetData())); + ++size; + } + + while (size > _partialObjectCacheSize) + { + mtp::debug("purging last entry from cache..."); + _objectCache.pop_back(); + --size; + } + } else + data = _session->GetPartialObject(objectId, begin, rsize); + } + mtp::debug("read ", data.size(), " bytes of data"); + FUSE_CALL(fuse_reply_buf(req, static_cast(static_cast(data.data())), data.size())); + } + + void Write(fuse_req_t req, FuseId inode, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) + { + mtp::scoped_mutex_lock l(_mutex); + + struct stat attr = GetObjectAttr(inode); + mtp::ObjectId objectId = FromFuse(inode); + + ObjectEditSessionPtr tr = GetTransaction(inode); + + off_t newSize = off + size; + if (newSize > attr.st_size) + { + mtp::debug("truncating file to ", newSize); + tr->Truncate(newSize); + _objectAttrs[objectId].st_size = newSize; + } + + tr->Send(off, mtp::ByteArray(buf, buf + size)); + FUSE_CALL(fuse_reply_write(req, size)); + } + + void Create(fuse_req_t req, FuseId parent, const char *name, mode_t mode, struct fuse_file_info *fi) + { mtp::scoped_mutex_lock l(_mutex); CreateObject(mtp::ObjectFormat::Undefined, req, parent, name, mode, fi); } + + void MakeNode(fuse_req_t req, FuseId parent, const char *name, mode_t mode, dev_t rdev) + { mtp::scoped_mutex_lock l(_mutex); CreateObject(mtp::ObjectFormat::Undefined, req, parent, name, mode); } + + void MakeDir(fuse_req_t req, FuseId parent, const char *name, mode_t mode) + { mtp::scoped_mutex_lock l(_mutex); CreateObject(mtp::ObjectFormat::Association, req, parent, name, mode); } + + void Open(fuse_req_t req, FuseId ino, struct fuse_file_info *fi) + { + mtp::scoped_mutex_lock l(_mutex); + mtp::ObjectFormat format; + + try + { + format = static_cast(_session->GetObjectIntegerProperty(FromFuse(ino), mtp::ObjectProperty::ObjectFormat)); + } + catch(const std::exception &ex) + { FUSE_CALL(fuse_reply_err(req, ENOENT)); return; } + + if (format == mtp::ObjectFormat::Association) + { + FUSE_CALL(fuse_reply_err(req, EISDIR)); + return; + } + FUSE_CALL(fuse_reply_open(req, fi)); + } + + void ReleaseTransaction(FuseId ino) + { + auto i = _openedFiles.find(ino); + if (i != _openedFiles.end()) + _openedFiles.erase(i); + } + + void Release(fuse_req_t req, FuseId ino, struct fuse_file_info *fi) + { + mtp::scoped_mutex_lock l(_mutex); + ReleaseTransaction(ino); + FUSE_CALL(fuse_reply_err(req, 0)); + } + + void SetAttr(fuse_req_t req, FuseId inode, struct stat *attr, int to_set, struct fuse_file_info *fi) + { + mtp::scoped_mutex_lock l(_mutex); + FuseEntry entry(req); + if (FillEntry(entry, inode)) + { + if (to_set & FUSE_SET_ATTR_SIZE) + { + off_t newSize = attr->st_size; + ObjectEditSessionPtr tr = GetTransaction(inode); + tr->Truncate(newSize); + entry.attr.st_size = newSize; + _objectAttrs[FromFuse(inode)].st_size = newSize; + } + entry.ReplyAttr(); + } + else + entry.ReplyError(ENOENT); + } + + void UnlinkImpl(FuseId inode) + { + mtp::debug(" unlinking inode ", inode.Inode); + mtp::ObjectId id = FromFuse(inode); + _session->DeleteObject(id); + _openedFiles.erase(inode); + _objectAttrs.erase(id); + } + + void Unlink(fuse_req_t req, FuseId parent, const char *name) + { + mtp::scoped_mutex_lock l(_mutex); + ChildrenObjects &children = GetChildren(parent); + auto i = children.find(name); + if (i == children.end()) + { + FUSE_CALL(fuse_reply_err(req, ENOENT)); + return; + } + + FuseId inode = i->second; + UnlinkImpl(inode); + _directoryCache.erase(parent); + children.erase(i); + + FUSE_CALL(fuse_reply_err(req, 0)); + } + + void RemoveDir (fuse_req_t req, FuseId parent, const char *name) + { Unlink(req, parent, name); } + + void Rename(fuse_req_t req, FuseId parent, const char *name, FuseId newparent, const char *newName) + { + if (parent != newparent) { + //no renames across directory boundary, sorry + //return cross-device link, so user space should re-create file and copy it + FUSE_CALL(fuse_reply_err(req, EXDEV)); + return; + } + + mtp::scoped_mutex_lock l(_mutex); + ChildrenObjects &children = GetChildren(parent); + auto i = children.find(name); + if (i == children.end()) + { + FUSE_CALL(fuse_reply_err(req, ENOENT)); + return; + } + + FuseId inode = i->second; + mtp::debug(" renaming inode ", inode.Inode, " to ", newName); + + auto old = children.find(newName); + if (old != children.end()) + { + mtp::debug(" unlinking target inode ", old->second.Inode); + UnlinkImpl(old->second); + children.erase(old); + } + + mtp::ObjectId id = FromFuse(inode); + _session->SetObjectProperty(id, mtp::ObjectProperty::ObjectFilename, std::string(newName)); + + children.erase(i); + children.emplace(newName, inode); + _directoryCache.erase(parent); + + FUSE_CALL(fuse_reply_err(req, 0)); + } + + void StatFS(fuse_req_t req, FuseId ino) + { + mtp::scoped_mutex_lock l(_mutex); + struct statvfs stat = { }; + stat.f_namemax = 254; + + mtp::u64 freeSpace = 0, capacity = 0; + if (ino == FuseId::Root) + { + for(auto storageId : _storageIdList) + { + mtp::msg::StorageInfo si = _session->GetStorageInfo(storageId); + freeSpace += si.FreeSpaceInBytes; + capacity += si.MaxCapacity; + } + } + else + { + mtp::StorageId storageId; + if (IsStorage(ino)) + storageId = FuseIdToStorageId(ino); + else + storageId = _session->GetObjectStorage(FromFuse(ino)); + + mtp::msg::StorageInfo si = _session->GetStorageInfo(storageId); + freeSpace = si.FreeSpaceInBytes; + capacity = si.MaxCapacity; + } + + stat.f_frsize = stat.f_bsize = 1024 * 1024; + stat.f_blocks = capacity / stat.f_frsize; + stat.f_bfree = stat.f_bavail = freeSpace / stat.f_frsize; + + FUSE_CALL(fuse_reply_statfs(req, &stat)); + } + }; + + std::unique_ptr g_wrapper; + +#define WRAP_EX(...) do { \ + try { return __VA_ARGS__ ; } \ + catch (const mtp::usb::DeviceNotFoundException &) \ + { \ + g_wrapper->Connect(); \ + __VA_ARGS__ ; \ + } \ + catch (const std::exception &ex) \ + { mtp::error(#__VA_ARGS__ " failed: ", ex.what()); fuse_reply_err(req, EIO); } \ + } while(false) + + void Init (void *userdata, struct fuse_conn_info *conn) + { + mtp::debug("Init: fuse proto version: ", conn->proto_major, ".", conn->proto_minor, + ", capability: 0x", mtp::hex(conn->capable, 8), + ", async read: ", conn->async_read, + //", congestion_threshold: ", conn->congestion_threshold, + //", max bg: ", conn->max_background, + ", max readahead: ", conn->max_readahead, ", max write: ", conn->max_write + ); + + //If synchronous reads are chosen, Fuse will wait for reads to complete before issuing any other requests. + //mtp is completely synchronous. you cannot have two transaction in parallel, so you have to wait any operation to finish before starting another one + + conn->async_read = 0; + conn->want &= ~FUSE_CAP_ASYNC_READ; + try { g_wrapper->Init(userdata, conn); } catch (const std::exception &ex) { mtp::error("init failed:", ex.what()); } + } + + void Lookup (fuse_req_t req, fuse_ino_t parent, const char *name) + { mtp::debug(" Lookup ", parent, " ", name); WRAP_EX(g_wrapper->Lookup(req, FuseId(parent), name)); } + + void ReadDir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) + { mtp::debug(" Readdir ", ino, " ", size, " ", off); WRAP_EX(g_wrapper->ReadDir(req, FuseId(ino), size, off, fi)); } + + void GetAttr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) + { mtp::debug(" GetAttr ", ino); WRAP_EX(g_wrapper->GetAttr(req, FuseId(ino), fi)); } + + void SetAttr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi) + { mtp::debug(" SetAttr ", ino, " 0x", mtp::hex(to_set, 8)); WRAP_EX(g_wrapper->SetAttr(req, FuseId(ino), attr, to_set, fi)); } + + void Read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) + { mtp::debug(" Read ", ino, " ", size, " ", off); WRAP_EX(g_wrapper->Read(req, FuseId(ino), size, off, fi)); } + + void Write(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) + { mtp::debug(" Write ", ino, " ", size, " ", off); WRAP_EX(g_wrapper->Write(req, FuseId(ino), buf, size, off, fi)); } + + void MakeNode(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev) + { mtp::debug(" MakeNode ", parent, " ", name, " 0x", mtp::hex(mode, 8)); WRAP_EX(g_wrapper->MakeNode(req, FuseId(parent), name, mode, rdev)); } + + void Create(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi) + { mtp::debug(" Create ", parent, " ", name, " 0x", mtp::hex(mode, 8)); WRAP_EX(g_wrapper->Create(req, FuseId(parent), name, mode, fi)); } + + void Open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) + { mtp::debug(" Open ", ino); WRAP_EX(g_wrapper->Open(req, FuseId(ino), fi)); } + + void Rename(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname) + { mtp::debug(" Rename ", parent, " ", name, " -> ", newparent, " ", newname); WRAP_EX(g_wrapper->Rename(req, FuseId(parent), name, FuseId(newparent), newname)); } + + void Release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) + { mtp::debug(" Release ", ino); WRAP_EX(g_wrapper->Release(req, FuseId(ino), fi)); } + + void MakeDir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode) + { mtp::debug(" MakeDir ", parent, " ", name, " 0x", mtp::hex(mode, 8)); WRAP_EX(g_wrapper->MakeDir(req, FuseId(parent), name, mode)); } + + void RemoveDir (fuse_req_t req, fuse_ino_t parent, const char *name) + { mtp::debug(" RemoveDir ", parent, " ", name); WRAP_EX(g_wrapper->RemoveDir(req, FuseId(parent), name)); } + + void Unlink(fuse_req_t req, fuse_ino_t parent, const char *name) + { mtp::debug(" Unlink ", parent, " ", name); WRAP_EX(g_wrapper->Unlink(req, FuseId(parent), name)); } + + void StatFS(fuse_req_t req, fuse_ino_t ino) + { mtp::debug(" StatFS ", ino); WRAP_EX(g_wrapper->StatFS(req, FuseId(ino))); } +} + +int main(int argc, char **argv) +{ + std::string deviceFilter; + bool claimInterface = true; + bool resetDevice = false; + + std::vector args; + args.push_back(argv[0]); + + for(int i = 1; i < argc; ++i) + { + if (strcmp(argv[i], "-R") == 0) + resetDevice = true; + else if (strcmp(argv[i], "-C") == 0) + claimInterface = false; + else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "-odebug") == 0) + { + args.push_back(argv[i]); + mtp::g_debug = true; + } + else if (strcmp(argv[i], "-o") == 0 && strcmp(argv[i + 1], "debug") == 0) + { + if (i + 1 == argc) + { + mtp::error("-o requires an argument"); + return 1; + } + mtp::g_debug = true; + args.push_back(argv[i]); + } + else if (strcmp(argv[i], "-D") == 0) + { + if (i + 1 == argc) + { + mtp::error("-D requires an argument"); + return 1; + } + deviceFilter = argv[i + 1]; + ++i; + } + else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) + { + mtp::print(); + mtp::print("Additional AFT options: "); + mtp::print(" -R reset device"); + mtp::print(" -C do not claim USB interface"); + mtp::print(" -d / -o debug show MTP debug output"); + mtp::print(" -D filter by manufacturer/model/serial"); + mtp::print(" -v AFT verbose output"); + return 0; + } else + args.push_back(argv[i]); + } + + args.push_back(nullptr); + + try + { g_wrapper.reset(new FuseWrapper(deviceFilter, claimInterface, resetDevice)); } + catch(const std::exception &ex) + { mtp::error("connect failed: ", ex.what()); return 1; } + + struct fuse_lowlevel_ops ops = {}; + + ops.init = &Init; + ops.lookup = &Lookup; + ops.readdir = &ReadDir; + ops.getattr = &GetAttr; + ops.setattr = &SetAttr; + ops.mknod = &MakeNode; + ops.open = &Open; + ops.create = &Create; + ops.read = &Read; + ops.write = &Write; + ops.mkdir = &MakeDir; + ops.rename = &Rename; + ops.release = &Release; + ops.rmdir = &RemoveDir; + ops.unlink = &Unlink; + ops.statfs = &StatFS; + + struct fuse_args fuse_args = FUSE_ARGS_INIT(static_cast(args.size() - 1), args.data()); + struct fuse_chan *ch; + char *mountpoint; + int err = -1; + int multithreaded = 0, foreground = 0; + + if (fuse_parse_cmdline(&fuse_args, &mountpoint, &multithreaded, &foreground) != -1) + { + if (!mountpoint) + { + auto mp = g_wrapper->GetMountpoint(); + mountpoint = strdup(mp.c_str()); + mkdir(mountpoint, 0700); + } + + if (mountpoint != NULL && (ch = fuse_mount(mountpoint, &fuse_args)) != NULL) + { + struct fuse_session *se = fuse_lowlevel_new(&fuse_args, &ops, sizeof(ops), NULL); + if (se != NULL) + { + if (fuse_set_signal_handlers(se) != -1) + { + fuse_session_add_chan(se, ch); + if (fuse_daemonize(foreground) == -1) + perror("fuse_daemonize"); + err = (multithreaded? fuse_session_loop_mt: fuse_session_loop)(se); + fuse_remove_signal_handlers(se); + fuse_session_remove_chan(ch); + } + fuse_session_destroy(se); + } + fuse_unmount(mountpoint, ch); + } + } else { + mtp::error("fuse_parse_cmdline failed"); + } + fuse_opt_free_args(&fuse_args); + + return err ? 1 : 0; +} diff -Nru android-file-transfer-4.2/fuse/fuse_ll.cpp android-file-transfer-4.3/fuse/fuse_ll.cpp --- android-file-transfer-4.2/fuse/fuse_ll.cpp 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/fuse/fuse_ll.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,982 +0,0 @@ -/* - This file is part of Android File Transfer For Linux. - Copyright (C) 2015-2020 Vladimir Menshakov - - This library is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, - or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this library; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace -{ - using namespace mtp::fuse; - - class FuseWrapper - { - std::mutex _mutex; - std::string _deviceFilter; - bool _claimInterface; - bool _resetDevice; - mtp::DevicePtr _device; - mtp::SessionPtr _session; - bool _editObjectSupported; - bool _getObjectPropertyListSupported; - bool _getPartialObjectSupported; - time_t _connectTime; - size_t _partialObjectCacheSize; - - static constexpr size_t DefaultPartialObjectCacheSize = 3; //just 3 object entries. - - using ChildrenObjects = std::map; - using Files = std::map; - Files _files; - - typedef std::map ObjectAttrs; - ObjectAttrs _objectAttrs; - - typedef mtp::Session::ObjectEditSessionPtr ObjectEditSessionPtr; - typedef std::map OpenedFiles; - OpenedFiles _openedFiles; - - typedef std::map DirectoryCache; - DirectoryCache _directoryCache; - - static const size_t MtpStorageShift = FUSE_ROOT_ID + 1; - static const size_t MtpObjectShift = 999998 + MtpStorageShift; - - std::vector _storageIdList; - std::map _storageToName; - std::map _storageFromName; - - struct ObjectCacheEntry - { - mtp::ObjectId Id; - mtp::ByteArray Data; - - ObjectCacheEntry() - { } - - ObjectCacheEntry(mtp::ObjectId id, mtp::ByteArray && data): Id(id), Data(std::move(data)) - { } - }; - - using ObjectCache = std::list; - ObjectCache _objectCache; - - private: - static FuseId ToFuse(mtp::ObjectId id) - { return FuseId(id.Id + MtpObjectShift); } - - static mtp::ObjectId FromFuse(FuseId id) - { return mtp::ObjectId(id.Inode - MtpObjectShift); } - - static bool IsStorage(FuseId id) - { return id.Inode >= MtpStorageShift && id.Inode <= MtpObjectShift; } - - mtp::StorageId FuseIdToStorageId(FuseId id) const - { - if (!IsStorage(id)) - throw std::runtime_error("converting non-storage inode to storage id"); - size_t i = id.Inode - MtpStorageShift; - if (i >= _storageIdList.size()) - throw std::runtime_error("invalid storage id"); - return _storageIdList[i]; - } - - FuseId FuseIdFromStorageId(mtp::StorageId storageId) const - { - auto i = std::find(_storageIdList.begin(), _storageIdList.end(), storageId); - if (i == _storageIdList.end()) - throw std::runtime_error("invalid storage id"); - return FuseId(MtpStorageShift + std::distance(_storageIdList.begin(), i)); - } - - void GetObjectInfo(ChildrenObjects &cache, mtp::ObjectId id) - { - auto oi = _session->GetObjectInfo(id); - - FuseId inode = ToFuse(id); - cache.emplace(oi.Filename, inode); - - struct stat &attr = _objectAttrs[id]; - attr.st_ino = inode.Inode; - attr.st_mode = FuseEntry::GetMode(oi.ObjectFormat); - attr.st_atime = attr.st_mtime = mtp::ConvertDateTime(oi.ModificationDate); - attr.st_ctime = mtp::ConvertDateTime(oi.CaptureDate); - attr.st_size = oi.ObjectCompressedSize != mtp::MaxObjectSize? oi.ObjectCompressedSize: _session->GetObjectIntegerProperty(id, mtp::ObjectProperty::ObjectSize); - } - - struct stat GetObjectAttr(FuseId inode) - { - if (inode == FuseId::Root) - { - struct stat attr = { }; - attr.st_ino = inode.Inode; - attr.st_mtime = attr.st_ctime = attr.st_atime = _connectTime; - attr.st_mode = FuseEntry::DirectoryMode; - return attr; - } - - if (IsStorage(inode)) - { - struct stat attr = { }; - attr.st_ino = inode.Inode; - attr.st_mtime = attr.st_ctime = attr.st_atime = _connectTime; - attr.st_mode = FuseEntry::DirectoryMode; - return attr; - } - - mtp::ObjectId id = FromFuse(inode); - auto i = _objectAttrs.find(id); - if (i != _objectAttrs.end()) - return i->second; - - //populate cache for parent - auto parent = GetParentObject(inode); - GetChildren(parent); //populate cache - - i = _objectAttrs.find(id); - if (i != _objectAttrs.end()) - return i->second; - else - throw std::runtime_error("no such object"); - } - - template - void GetObjectPropertyList(mtp::ObjectId parent, const std::set &originalObjectList, const mtp::ObjectProperty property, - const std::function &callback) - { - std::set objectList(originalObjectList); - mtp::ByteArray data = _session->GetObjectPropertyList(parent, mtp::ObjectFormat::Any, property, 0, 1); - mtp::ObjectPropertyListParser parser; - - parser.Parse(data, [&objectList, &callback, property](mtp::ObjectId objectId, mtp::ObjectProperty p, const PropertyValueType & value) { - auto it = objectList.find(objectId); - if (property == p && it != objectList.end()) - { - objectList.erase(it); - try { callback(objectId, value); } catch(const std::exception &ex) { mtp::error("callback for property list 0x", mtp::hex(property, 4), " failed: ", ex.what()); } - } - else - mtp::debug("extra property 0x", hex(p, 4), " returned for object ", objectId.Id, ", while querying property list 0x", mtp::hex(property, 4)); - }); - - if (!objectList.empty()) - { - mtp::error("inconsistent GetObjectPropertyList for property 0x", mtp::hex(property, 4)); - for(auto objectId : objectList) - { - mtp::debug("querying property 0x", mtp::hex(property, 4), " for object ", objectId); - try - { - mtp::ByteArray data = _session->GetObjectProperty(objectId, property); - mtp::InputStream stream(data); - PropertyValueType value = PropertyValueType(); - stream >> value; - callback(objectId, value); - } - catch(const std::exception &ex) { mtp::error("fallback query/callback for property 0x", mtp::hex(property, 4), " failed: ", ex.what()); } - } - } - } - - ChildrenObjects & GetChildren(FuseId inode) - { - if (inode == FuseId::Root) - { - PopulateStorages(); - ChildrenObjects & cache = _files[inode]; - cache.clear(); - for(size_t i = 0; i < _storageIdList.size(); ++i) - { - mtp::StorageId storageId = _storageIdList[i]; - auto name = _storageToName.find(storageId); - if (name != _storageToName.end()) - cache.emplace(name->second, FuseId(MtpStorageShift + i)); - else - mtp::error("no storage name for ", storageId); - } - return cache; - } - - { - auto i = _files.find(inode); - if (i != _files.end()) - return i->second; - } - - ChildrenObjects & cache = _files[inode]; - - using namespace mtp; - if (inode == FuseId::Root) - { - return cache; - } - - msg::ObjectHandles oh; - - if (IsStorage(inode)) - { - mtp::StorageId storageId = FuseIdToStorageId(inode); - oh = _session->GetObjectHandles(storageId, mtp::ObjectFormat::Any, mtp::Session::Root); - } - else - { - mtp::ObjectId parent = FromFuse(inode); - oh = _session->GetObjectHandles(mtp::Session::AllStorages, mtp::ObjectFormat::Any, parent); - - if (_getObjectPropertyListSupported) - { - std::set objects; - for(auto id : oh.ObjectHandles) - objects.insert(id); - - //populate filenames - GetObjectPropertyList(parent, objects, mtp::ObjectProperty::ObjectFilename, - [&cache](ObjectId objectId, const std::string &name) - { cache.emplace(name, ToFuse(objectId)); }); - - //format - GetObjectPropertyList(parent, objects, mtp::ObjectProperty::ObjectFormat, - [this](ObjectId objectId, mtp::ObjectFormat format) - { - struct stat & attr = _objectAttrs[objectId]; - attr.st_ino = ToFuse(objectId).Inode; - attr.st_mode = FuseEntry::GetMode(format); - }); - - //size - GetObjectPropertyList(parent, objects, mtp::ObjectProperty::ObjectSize, - [this](ObjectId objectId, mtp::u64 size) - { _objectAttrs[objectId].st_size = size; }); - - //mtime - try - { - GetObjectPropertyList(parent, objects, mtp::ObjectProperty::DateModified, - [this](ObjectId objectId, const std::string & mtime) - { _objectAttrs[objectId].st_mtime = mtp::ConvertDateTime(mtime); }); - } - catch(const std::exception &ex) - { } - - //ctime - try - { - GetObjectPropertyList(parent, objects, mtp::ObjectProperty::DateAdded, - [this](ObjectId objectId, const std::string & ctime) - { _objectAttrs[objectId].st_ctime = mtp::ConvertDateTime(ctime); }); - } - catch(const std::exception &ex) - { } - - return cache; - } - } - - for(auto id : oh.ObjectHandles) - { - try - { - GetObjectInfo(cache, id); - } catch(const std::exception &ex) - { } - } - return cache; - } - - FuseId CreateObject(FuseId parentInode, const std::string &filename, mtp::ObjectFormat format) - { - mtp::ObjectId parentId = FromFuse(parentInode); - mtp::StorageId storageId; - if (IsStorage(parentInode)) - { - storageId = FuseIdToStorageId(parentInode); - parentId = mtp::Session::Root; - } - else - storageId = _session->GetObjectStorage(parentId); - mtp::debug(" creating object in storage ", mtp::hex(storageId.Id), ", parent: ", mtp::hex(parentId.Id, 8)); - - mtp::msg::NewObjectInfo noi; - if (format != mtp::ObjectFormat::Association) - { - mtp::msg::ObjectInfo oi; - oi.Filename = filename; - oi.ObjectFormat = format; - noi = _session->SendObjectInfo(oi, storageId, parentId); - _session->SendObject(std::make_shared(mtp::ByteArray())); - } - else - noi = _session->CreateDirectory(filename, parentId, storageId); - - mtp::debug(" new object id ", noi.ObjectId.Id); - - { //update cache: - auto i = _files.find(parentInode); - if (i != _files.end()) - GetObjectInfo(i->second, noi.ObjectId); //insert object info into cache - _directoryCache.erase(parentInode); - } - return ToFuse(noi.ObjectId); - } - - void CreateObject(mtp::ObjectFormat format, fuse_req_t req, FuseId parentId, const char *name, mode_t mode, fuse_file_info *createInfo = NULL) - { - if (parentId == FuseId::Root) - { - FUSE_CALL(fuse_reply_err(req, EPERM)); //cannot create files in the same level with storages - return; - } - auto objectId = CreateObject(parentId, name, format); - FuseEntry entry(req); - entry.SetId(objectId); - entry.attr = GetObjectAttr(objectId); - - if (createInfo) - entry.ReplyCreate(createInfo); - else - entry.Reply(); - } - - FuseId GetParentObject(FuseId inode) - { - if (inode == FuseId::Root) - return inode; - - if (IsStorage(inode)) - return FuseId::Root; - - mtp::ObjectId id = FromFuse(inode); - mtp::ObjectId parent = _session->GetObjectParent(id); - if (parent == mtp::Session::Device || parent == mtp::Session::Root) //parent == root -> storage - { - return FuseIdFromStorageId(_session->GetObjectStorage(id)); - } - else - return ToFuse(parent); - } - - bool FillEntry(FuseEntry &entry, FuseId id) - { - try { entry.attr = GetObjectAttr(id); } catch(const std::exception &ex) { return false; } - entry.SetId(id); - return true; - } - - public: - FuseWrapper(const std::string & deviceFilter, bool claimInterface, bool resetDevice): - _deviceFilter(deviceFilter), _claimInterface(claimInterface), _resetDevice(resetDevice), _partialObjectCacheSize(DefaultPartialObjectCacheSize) - { Connect(); } - - void Connect() - { - mtp::scoped_mutex_lock l(_mutex); - - _openedFiles.clear(); - _files.clear(); - _objectAttrs.clear(); - _directoryCache.clear(); - _session.reset(); - _device.reset(); - _device = mtp::Device::FindFirst(_deviceFilter, _claimInterface, _resetDevice); - if (!_device) - throw std::runtime_error("no MTP device found"); - - _session = _device->OpenSession(1); - _editObjectSupported = _session->EditObjectSupported(); - if (!_editObjectSupported) - mtp::error("your device does not have android EditObject extension, mounting read-only"); - _getObjectPropertyListSupported = _session->GetObjectPropertyListSupported(); - if (!_getObjectPropertyListSupported) - mtp::error("your device does not have GetObjectPropertyList extension, expect slow enumeration of big directories"); - _getPartialObjectSupported = _session->GetDeviceInfo().Supports(mtp::OperationCode::GetPartialObject); - if (!_getPartialObjectSupported) - mtp::error("your device does not have GetPartialObject extension (beware, this will download the latest N objects you accessed and keep them in a small in-memory cache)"); - - _connectTime = time(NULL); - PopulateStorages(); - } - - void PopulateStorages() - { - _storageIdList.clear(); - _storageFromName.clear(); - _storageToName.clear(); - mtp::msg::StorageIDs ids = _session->GetStorageIDs(); - _storageIdList.reserve(ids.StorageIDs.size()); - for(size_t i = 0; i < ids.StorageIDs.size(); ++i) - { - mtp::StorageId id = ids.StorageIDs[i]; - mtp::msg::StorageInfo si = _session->GetStorageInfo(id); - std::string path = si.GetName(); - if (path.empty()) - { - char buf[64]; - snprintf(buf, sizeof(buf), "sdcard%u", (unsigned)i); - path = buf; - } - FuseId inode(MtpStorageShift + i); - _storageIdList.push_back(id); - _storageFromName[path] = id; - _storageToName[id] = path; - } - } - std::string GetMountpoint() - { return _session->GetDeviceInfo().GetFilesystemFriendlyName(); } - - void Init(void *, fuse_conn_info *conn) - { -#if 0 - /* - Disable FUSE_CAP_BIG_WRITES until further investigation - https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.4&id=1fb027d7596464d3fad3ed59f70f43807ef926c6 - https://github.com/netheril96/securefs/commit/e5b9e5d41a6dcdee60ab4a18b46b9a0c6475ba21 - */ - mtp::scoped_mutex_lock l(_mutex); - conn->want |= conn->capable & FUSE_CAP_BIG_WRITES; //big writes - static const size_t MaxWriteSize = 1024 * 1024; - if (conn->max_write < MaxWriteSize) - conn->max_write = MaxWriteSize; -#endif - } - - void Lookup (fuse_req_t req, FuseId parent, const char *name) - { - mtp::scoped_mutex_lock l(_mutex); - FuseEntry entry(req); - - const ChildrenObjects & children = GetChildren(parent); - auto it = children.find(name); - if (it != children.end()) - { - if (FillEntry(entry, it->second)) - { - entry.Reply(); - return; - } - } - entry.ReplyError(ENOENT); - } - - void ReadDir(fuse_req_t req, FuseId ino, size_t size, off_t off, struct fuse_file_info *fi) - { - mtp::scoped_mutex_lock l(_mutex); - if (!(GetObjectAttr(ino).st_mode & S_IFDIR)) - { - FUSE_CALL(fuse_reply_err(req, ENOTDIR)); - return; - } - - FuseDirectory dir(req); - auto it = _directoryCache.find(ino); - if (it == _directoryCache.end()) - { - const ChildrenObjects & cache = GetChildren(ino); - - it = _directoryCache.insert(std::make_pair(ino, CharArray())).first; - CharArray &data = it->second; - - dir.Add(data, ".", GetObjectAttr(FuseId::Root)); - dir.Add(data, "..", GetObjectAttr(GetParentObject(ino))); - for(auto entry : cache) - { - dir.Add(data, entry.first, GetObjectAttr(entry.second)); - } - } - - dir.Reply(req, it->second, off, size); - } - - void GetAttr(fuse_req_t req, FuseId ino, struct fuse_file_info *fi) - { - mtp::scoped_mutex_lock l(_mutex); - FuseEntry entry(req); - if (FillEntry(entry, ino)) - entry.ReplyAttr(); - else - entry.ReplyError(ENOENT); - } - - mtp::Session::ObjectEditSessionPtr GetTransaction(FuseId inode) - { - mtp::Session::ObjectEditSessionPtr tr; - { - auto it = _openedFiles.find(inode); - if (it != _openedFiles.end()) - tr = it->second; - else - { - tr = mtp::Session::EditObject(_session, FromFuse(inode)); - _openedFiles[inode] = tr; - } - } - return NOT_NULL(tr); - } - - void Read(fuse_req_t req, FuseId ino, size_t size, off_t begin, struct fuse_file_info *fi) - { - mtp::scoped_mutex_lock l(_mutex); - ReleaseTransaction(ino); - struct stat attr = GetObjectAttr(ino); - off_t rsize = std::min(attr.st_size - begin, size); - mtp::debug("reading ", rsize, " bytes"); - mtp::ByteArray data; - auto objectId = FromFuse(ino); - if (rsize > 0) - { - if (!_getPartialObjectSupported) - { - std::size_t size = 0; - - ObjectCache::iterator it; - for(it = _objectCache.begin(); it != _objectCache.end(); ++it, ++size) - { - auto & entry = *it; - if (entry.Id == objectId) - { - mtp::debug("in-memory cache hit"); - auto src = entry.Data.data() + begin; - std::copy(src, src + rsize, std::back_inserter(data)); - if (it != _objectCache.begin()) - std::swap(*_objectCache.begin(), *it); - break; - } - } - if (it == _objectCache.end()) - { - mtp::debug("in-memory cache miss"); - auto stream = std::make_shared(); - _session->GetObject(objectId, stream); - auto src = stream->GetData().data() + begin; - std::copy(src, src + rsize, std::back_inserter(data)); - _objectCache.emplace_front(objectId, std::move(stream->GetData())); - ++size; - } - - while (size > _partialObjectCacheSize) - { - mtp::debug("purging last entry from cache..."); - _objectCache.pop_back(); - --size; - } - } else - data = _session->GetPartialObject(objectId, begin, rsize); - } - mtp::debug("read ", data.size(), " bytes of data"); - FUSE_CALL(fuse_reply_buf(req, static_cast(static_cast(data.data())), data.size())); - } - - void Write(fuse_req_t req, FuseId inode, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) - { - mtp::scoped_mutex_lock l(_mutex); - - struct stat attr = GetObjectAttr(inode); - mtp::ObjectId objectId = FromFuse(inode); - - ObjectEditSessionPtr tr = GetTransaction(inode); - - off_t newSize = off + size; - if (newSize > attr.st_size) - { - mtp::debug("truncating file to ", newSize); - tr->Truncate(newSize); - _objectAttrs[objectId].st_size = newSize; - } - - tr->Send(off, mtp::ByteArray(buf, buf + size)); - FUSE_CALL(fuse_reply_write(req, size)); - } - - void Create(fuse_req_t req, FuseId parent, const char *name, mode_t mode, struct fuse_file_info *fi) - { mtp::scoped_mutex_lock l(_mutex); CreateObject(mtp::ObjectFormat::Undefined, req, parent, name, mode, fi); } - - void MakeNode(fuse_req_t req, FuseId parent, const char *name, mode_t mode, dev_t rdev) - { mtp::scoped_mutex_lock l(_mutex); CreateObject(mtp::ObjectFormat::Undefined, req, parent, name, mode); } - - void MakeDir(fuse_req_t req, FuseId parent, const char *name, mode_t mode) - { mtp::scoped_mutex_lock l(_mutex); CreateObject(mtp::ObjectFormat::Association, req, parent, name, mode); } - - void Open(fuse_req_t req, FuseId ino, struct fuse_file_info *fi) - { - mtp::scoped_mutex_lock l(_mutex); - mtp::ObjectFormat format; - - try - { - format = static_cast(_session->GetObjectIntegerProperty(FromFuse(ino), mtp::ObjectProperty::ObjectFormat)); - } - catch(const std::exception &ex) - { FUSE_CALL(fuse_reply_err(req, ENOENT)); return; } - - if (format == mtp::ObjectFormat::Association) - { - FUSE_CALL(fuse_reply_err(req, EISDIR)); - return; - } - FUSE_CALL(fuse_reply_open(req, fi)); - } - - void ReleaseTransaction(FuseId ino) - { - auto i = _openedFiles.find(ino); - if (i != _openedFiles.end()) - _openedFiles.erase(i); - } - - void Release(fuse_req_t req, FuseId ino, struct fuse_file_info *fi) - { - mtp::scoped_mutex_lock l(_mutex); - ReleaseTransaction(ino); - FUSE_CALL(fuse_reply_err(req, 0)); - } - - void SetAttr(fuse_req_t req, FuseId inode, struct stat *attr, int to_set, struct fuse_file_info *fi) - { - mtp::scoped_mutex_lock l(_mutex); - FuseEntry entry(req); - if (FillEntry(entry, inode)) - { - if (to_set & FUSE_SET_ATTR_SIZE) - { - off_t newSize = attr->st_size; - ObjectEditSessionPtr tr = GetTransaction(inode); - tr->Truncate(newSize); - entry.attr.st_size = newSize; - _objectAttrs[FromFuse(inode)].st_size = newSize; - } - entry.ReplyAttr(); - } - else - entry.ReplyError(ENOENT); - } - - void UnlinkImpl(FuseId inode) - { - mtp::debug(" unlinking inode ", inode.Inode); - mtp::ObjectId id = FromFuse(inode); - _session->DeleteObject(id); - _openedFiles.erase(inode); - _objectAttrs.erase(id); - } - - void Unlink(fuse_req_t req, FuseId parent, const char *name) - { - mtp::scoped_mutex_lock l(_mutex); - ChildrenObjects &children = GetChildren(parent); - auto i = children.find(name); - if (i == children.end()) - { - FUSE_CALL(fuse_reply_err(req, ENOENT)); - return; - } - - FuseId inode = i->second; - UnlinkImpl(inode); - _directoryCache.erase(parent); - children.erase(i); - - FUSE_CALL(fuse_reply_err(req, 0)); - } - - void RemoveDir (fuse_req_t req, FuseId parent, const char *name) - { Unlink(req, parent, name); } - - void Rename(fuse_req_t req, FuseId parent, const char *name, FuseId newparent, const char *newName) - { - if (parent != newparent) { - //no renames across directory boundary, sorry - //return cross-device link, so user space should re-create file and copy it - FUSE_CALL(fuse_reply_err(req, EXDEV)); - return; - } - - mtp::scoped_mutex_lock l(_mutex); - ChildrenObjects &children = GetChildren(parent); - auto i = children.find(name); - if (i == children.end()) - { - FUSE_CALL(fuse_reply_err(req, ENOENT)); - return; - } - - FuseId inode = i->second; - mtp::debug(" renaming inode ", inode.Inode, " to ", newName); - - auto old = children.find(newName); - if (old != children.end()) - { - mtp::debug(" unlinking target inode ", old->second.Inode); - UnlinkImpl(old->second); - children.erase(old); - } - - mtp::ObjectId id = FromFuse(inode); - _session->SetObjectProperty(id, mtp::ObjectProperty::ObjectFilename, std::string(newName)); - - children.erase(i); - children.emplace(newName, inode); - _directoryCache.erase(parent); - - FUSE_CALL(fuse_reply_err(req, 0)); - } - - void StatFS(fuse_req_t req, FuseId ino) - { - mtp::scoped_mutex_lock l(_mutex); - struct statvfs stat = { }; - stat.f_namemax = 254; - - mtp::u64 freeSpace = 0, capacity = 0; - if (ino == FuseId::Root) - { - for(auto storageId : _storageIdList) - { - mtp::msg::StorageInfo si = _session->GetStorageInfo(storageId); - freeSpace += si.FreeSpaceInBytes; - capacity += si.MaxCapacity; - } - } - else - { - mtp::StorageId storageId; - if (IsStorage(ino)) - storageId = FuseIdToStorageId(ino); - else - storageId = _session->GetObjectStorage(FromFuse(ino)); - - mtp::msg::StorageInfo si = _session->GetStorageInfo(storageId); - freeSpace = si.FreeSpaceInBytes; - capacity = si.MaxCapacity; - } - - stat.f_frsize = stat.f_bsize = 1024 * 1024; - stat.f_blocks = capacity / stat.f_frsize; - stat.f_bfree = stat.f_bavail = freeSpace / stat.f_frsize; - - FUSE_CALL(fuse_reply_statfs(req, &stat)); - } - }; - - std::unique_ptr g_wrapper; - -#define WRAP_EX(...) do { \ - try { return __VA_ARGS__ ; } \ - catch (const mtp::usb::DeviceNotFoundException &) \ - { \ - g_wrapper->Connect(); \ - __VA_ARGS__ ; \ - } \ - catch (const std::exception &ex) \ - { mtp::error(#__VA_ARGS__ " failed: ", ex.what()); fuse_reply_err(req, EIO); } \ - } while(false) - - void Init (void *userdata, struct fuse_conn_info *conn) - { - mtp::debug("Init: fuse proto version: ", conn->proto_major, ".", conn->proto_minor, - ", capability: 0x", mtp::hex(conn->capable, 8), - ", async read: ", conn->async_read, - //", congestion_threshold: ", conn->congestion_threshold, - //", max bg: ", conn->max_background, - ", max readahead: ", conn->max_readahead, ", max write: ", conn->max_write - ); - - //If synchronous reads are chosen, Fuse will wait for reads to complete before issuing any other requests. - //mtp is completely synchronous. you cannot have two transaction in parallel, so you have to wait any operation to finish before starting another one - - conn->async_read = 0; - conn->want &= ~FUSE_CAP_ASYNC_READ; - try { g_wrapper->Init(userdata, conn); } catch (const std::exception &ex) { mtp::error("init failed:", ex.what()); } - } - - void Lookup (fuse_req_t req, fuse_ino_t parent, const char *name) - { mtp::debug(" Lookup ", parent, " ", name); WRAP_EX(g_wrapper->Lookup(req, FuseId(parent), name)); } - - void ReadDir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) - { mtp::debug(" Readdir ", ino, " ", size, " ", off); WRAP_EX(g_wrapper->ReadDir(req, FuseId(ino), size, off, fi)); } - - void GetAttr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) - { mtp::debug(" GetAttr ", ino); WRAP_EX(g_wrapper->GetAttr(req, FuseId(ino), fi)); } - - void SetAttr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi) - { mtp::debug(" SetAttr ", ino, " 0x", mtp::hex(to_set, 8)); WRAP_EX(g_wrapper->SetAttr(req, FuseId(ino), attr, to_set, fi)); } - - void Read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) - { mtp::debug(" Read ", ino, " ", size, " ", off); WRAP_EX(g_wrapper->Read(req, FuseId(ino), size, off, fi)); } - - void Write(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) - { mtp::debug(" Write ", ino, " ", size, " ", off); WRAP_EX(g_wrapper->Write(req, FuseId(ino), buf, size, off, fi)); } - - void MakeNode(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev) - { mtp::debug(" MakeNode ", parent, " ", name, " 0x", mtp::hex(mode, 8)); WRAP_EX(g_wrapper->MakeNode(req, FuseId(parent), name, mode, rdev)); } - - void Create(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi) - { mtp::debug(" Create ", parent, " ", name, " 0x", mtp::hex(mode, 8)); WRAP_EX(g_wrapper->Create(req, FuseId(parent), name, mode, fi)); } - - void Open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) - { mtp::debug(" Open ", ino); WRAP_EX(g_wrapper->Open(req, FuseId(ino), fi)); } - - void Rename(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname) - { mtp::debug(" Rename ", parent, " ", name, " -> ", newparent, " ", newname); WRAP_EX(g_wrapper->Rename(req, FuseId(parent), name, FuseId(newparent), newname)); } - - void Release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) - { mtp::debug(" Release ", ino); WRAP_EX(g_wrapper->Release(req, FuseId(ino), fi)); } - - void MakeDir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode) - { mtp::debug(" MakeDir ", parent, " ", name, " 0x", mtp::hex(mode, 8)); WRAP_EX(g_wrapper->MakeDir(req, FuseId(parent), name, mode)); } - - void RemoveDir (fuse_req_t req, fuse_ino_t parent, const char *name) - { mtp::debug(" RemoveDir ", parent, " ", name); WRAP_EX(g_wrapper->RemoveDir(req, FuseId(parent), name)); } - - void Unlink(fuse_req_t req, fuse_ino_t parent, const char *name) - { mtp::debug(" Unlink ", parent, " ", name); WRAP_EX(g_wrapper->Unlink(req, FuseId(parent), name)); } - - void StatFS(fuse_req_t req, fuse_ino_t ino) - { mtp::debug(" StatFS ", ino); WRAP_EX(g_wrapper->StatFS(req, FuseId(ino))); } -} - -int main(int argc, char **argv) -{ - std::string deviceFilter; - bool claimInterface = true; - bool resetDevice = false; - bool showHelp = false; - - std::vector args; - args.push_back(argv[0]); - - for(int i = 1; i < argc; ++i) - { - if (strcmp(argv[i], "-R") == 0) - resetDevice = false; - else if (strcmp(argv[i], "-C") == 0) - claimInterface = false; - else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "-odebug") == 0) - { - args.push_back(argv[i]); - mtp::g_debug = true; - } - else if (i + 1 < argc && strcmp(argv[i], "-o") == 0 && strcmp(argv[i + 1], "debug") == 0) - { - mtp::g_debug = true; - args.push_back(argv[i]); - } - else if (i + 1 < argc && strcmp(argv[i], "-D") == 0) - { - deviceFilter = argv[i + 1]; - ++i; - } - else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) - { - args.push_back(argv[i]); - showHelp = true; - } else - args.push_back(argv[i]); - } - - args.push_back(nullptr); - - try - { g_wrapper.reset(new FuseWrapper(deviceFilter, claimInterface, resetDevice)); } - catch(const std::exception &ex) - { mtp::error("connect failed: ", ex.what()); return 1; } - - struct fuse_lowlevel_ops ops = {}; - - ops.init = &Init; - ops.lookup = &Lookup; - ops.readdir = &ReadDir; - ops.getattr = &GetAttr; - ops.setattr = &SetAttr; - ops.mknod = &MakeNode; - ops.open = &Open; - ops.create = &Create; - ops.read = &Read; - ops.write = &Write; - ops.mkdir = &MakeDir; - ops.rename = &Rename; - ops.release = &Release; - ops.rmdir = &RemoveDir; - ops.unlink = &Unlink; - ops.statfs = &StatFS; - - struct fuse_args fuse_args = FUSE_ARGS_INIT(static_cast(args.size() - 1), args.data()); - struct fuse_chan *ch; - char *mountpoint; - int err = -1; - int multithreaded = 0, foreground = 0; - - if (fuse_parse_cmdline(&fuse_args, &mountpoint, &multithreaded, &foreground) != -1) - { - if (!mountpoint) - { - auto mp = g_wrapper->GetMountpoint(); - mountpoint = strdup(mp.c_str()); - mkdir(mountpoint, 0700); - } - - if (mountpoint != NULL && (ch = fuse_mount(mountpoint, &fuse_args)) != NULL) - { - struct fuse_session *se = fuse_lowlevel_new(&fuse_args, &ops, sizeof(ops), NULL); - if (se != NULL) - { - if (fuse_set_signal_handlers(se) != -1) - { - fuse_session_add_chan(se, ch); - if (fuse_daemonize(foreground) == -1) - perror("fuse_daemonize"); - err = (multithreaded? fuse_session_loop_mt: fuse_session_loop)(se); - fuse_remove_signal_handlers(se); - fuse_session_remove_chan(ch); - } - fuse_session_destroy(se); - } - fuse_unmount(mountpoint, ch); - } - } else { - mtp::error("fuse_parse_cmdline failed"); - } - if (showHelp) { - mtp::print(); - mtp::print("Additional AFT options: "); - mtp::print(" -R reset device"); - mtp::print(" -C do not claim USB interface"); - mtp::print(" -v AFT verbose output"); - } - fuse_opt_free_args(&fuse_args); - - return err ? 1 : 0; -} diff -Nru android-file-transfer-4.2/mtp/backend/haiku/usb/Context.cpp android-file-transfer-4.3/mtp/backend/haiku/usb/Context.cpp --- android-file-transfer-4.2/mtp/backend/haiku/usb/Context.cpp 1970-01-01 00:00:00.000000000 +0000 +++ android-file-transfer-4.3/mtp/backend/haiku/usb/Context.cpp 2023-12-12 22:22:35.000000000 +0000 @@ -0,0 +1,33 @@ +/* + * Context.cpp + * Copyright (C) 2022 pulkomandy + * + * Distributed under terms of the MIT license. + */ + +#include "Context.h" + +namespace mtp { namespace usb +{ + Context::Context(int debugLevel) + : BUSBRoster() + { + Start(); + } + + Context::~Context() { + Stop(); + } + + status_t Context::DeviceAdded(BUSBDevice* device) { + _devices.emplace_back(std::make_shared(device)); + + return B_OK; + } + + void Context::DeviceRemoved(BUSBDevice* device) { + // TODO _devices.remove(device); + } + + +} }; diff -Nru android-file-transfer-4.2/mtp/backend/haiku/usb/Context.h android-file-transfer-4.3/mtp/backend/haiku/usb/Context.h --- android-file-transfer-4.2/mtp/backend/haiku/usb/Context.h 1970-01-01 00:00:00.000000000 +0000 +++ android-file-transfer-4.3/mtp/backend/haiku/usb/Context.h 2023-12-12 22:22:35.000000000 +0000 @@ -0,0 +1,55 @@ +/* + This file is part of Android File Transfer For Linux. + Copyright (C) 2015-2020 Vladimir Menshakov + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef AFTL_MTP_BACKEND_LIBUSB_USB_CONTEXT_H +#define AFTL_MTP_BACKEND_LIBUSB_USB_CONTEXT_H + +#include +#include +#include + +namespace mtp { namespace usb +{ + + class Context : public BUSBRoster, Noncopyable + { + public: + typedef std::vector Devices; + + private: + Devices _devices; + + status_t DeviceAdded(BUSBDevice*) final override; + void DeviceRemoved(BUSBDevice*) final override; + + public: + Context(int debugLevel = 3); + ~Context(); + + void Wait(); + + const Devices & GetDevices() const + { return _devices; } + }; + DECLARE_PTR(Context); + +}} + +#endif + diff -Nru android-file-transfer-4.2/mtp/backend/haiku/usb/Device.cpp android-file-transfer-4.3/mtp/backend/haiku/usb/Device.cpp --- android-file-transfer-4.2/mtp/backend/haiku/usb/Device.cpp 1970-01-01 00:00:00.000000000 +0000 +++ android-file-transfer-4.3/mtp/backend/haiku/usb/Device.cpp 2023-12-12 22:22:35.000000000 +0000 @@ -0,0 +1,81 @@ +/* + * Device.cpp + * Copyright (C) 2022 pulkomandy + * + * Distributed under terms of the MIT license. + */ + +#include "Device.h" + + +namespace mtp { namespace usb +{ + +Device::Device(ContextPtr ctx, BUSBDevice* handle) + : _context(ctx) + , _handle(handle) +{ +} + +Device::~Device() +{ +} + +void Device::Reset() +{ +} + +void Device::ReadControl(u8 type, u8 req, u16 value, u16 index, ByteArray &data, int timeout) +{ + fprintf(stderr, "Control read(type %x req %x value %x index %d size %lu) with timeout %d…", + type, req, value, index, data.size(), timeout); + ssize_t result = _handle->ControlTransfer(type, req, value, index, data.size(), const_cast(data.data())); + if (result >= 0) + data.resize(result); + else + throw std::runtime_error("read fail"); + fprintf(stderr, " complete (%ld).\n", result); +} + +void Device::WriteControl(u8 type, u8 req, u16 value, u16 index, const ByteArray &data, int timeout) +{ + fprintf(stderr, "Control write with timeout %d\n", timeout); + _handle->ControlTransfer(USB_REQTYPE_DEVICE_IN, req, value, index, data.size(), const_cast(data.data())); +} + +void Device::SetConfiguration(int idx) +{ + _handle->SetConfiguration(_handle->ConfigurationAt(idx)); +} + +void Device::WriteBulk(const EndpointPtr & ep, const IObjectInputStreamPtr &inputStream, int timeout) +{ + ByteArray data(inputStream->GetSize()); + inputStream->Read(data.data(), data.size()); + ssize_t tr = ep->_endpoint.BulkTransfer(data.data(), data.size()); + if (tr != (int)data.size()) + throw std::runtime_error("short write"); +} + +void Device::ReadBulk(const EndpointPtr & ep, const IObjectOutputStreamPtr &outputStream, int timeout) +{ + ByteArray data(ep->GetMaxPacketSize()); + ssize_t tr; + { + tr = ep->_endpoint.BulkTransfer(data.data(), data.size()); + outputStream->Write(data.data(), tr); + } + while(tr == (int)data.size()); +} + +void Device::ClearHalt(const EndpointPtr & ep) +{ + ep->_endpoint.ClearStall(); +} + +InterfaceTokenPtr Device::ClaimInterface(const InterfacePtr & interface) +{ + return InterfaceTokenPtr(); +} + +}} diff -Nru android-file-transfer-4.2/mtp/backend/haiku/usb/Device.h android-file-transfer-4.3/mtp/backend/haiku/usb/Device.h --- android-file-transfer-4.2/mtp/backend/haiku/usb/Device.h 1970-01-01 00:00:00.000000000 +0000 +++ android-file-transfer-4.3/mtp/backend/haiku/usb/Device.h 2023-12-12 22:22:35.000000000 +0000 @@ -0,0 +1,109 @@ +/* + This file is part of Android File Transfer For Linux. + Copyright (C) 2015-2020 Vladimir Menshakov + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef AFTL_MTP_BACKEND_LIBUSB_USB_DEVICE_H +#define AFTL_MTP_BACKEND_LIBUSB_USB_DEVICE_H + +#include +#include +#include +#include +#include +#include + +namespace mtp { namespace usb +{ + class Context; + DECLARE_PTR(Context); + + class Endpoint + { + const BUSBEndpoint & _endpoint; + + public: + Endpoint(const BUSBEndpoint & endpoint) : _endpoint(endpoint) { } + + u8 GetAddress() const + { return _endpoint.Index(); } + + int GetMaxPacketSize() const + { return _endpoint.MaxPacketSize(); } + + EndpointDirection GetDirection() const + { + if (_endpoint.IsInput()) + return EndpointDirection::In; + else + return EndpointDirection::Out; + } + + EndpointType GetType() const + { + if (_endpoint.IsBulk()) + return EndpointType::Bulk; + if (_endpoint.IsInterrupt()) + return EndpointType::Interrupt; + if (_endpoint.IsIsochronous()) + return EndpointType::Isochronous; + return EndpointType::Control; + } + + friend class Device; + }; + DECLARE_PTR(Endpoint); + + class Interface; + DECLARE_PTR(Interface); + class InterfaceToken; + DECLARE_PTR(InterfaceToken); + + class Device : Noncopyable + { + private: + ContextPtr _context; + BUSBDevice * _handle; + + public: + Device(ContextPtr ctx, BUSBDevice * handle); + ~Device(); + + BUSBDevice * GetHandle() + { return _handle; } + + InterfaceTokenPtr ClaimInterface(const InterfacePtr & interface); + + void Reset(); + int GetConfiguration() const; + void SetConfiguration(int idx); + + void WriteBulk(const EndpointPtr & ep, const IObjectInputStreamPtr &inputStream, int timeout); + void ReadBulk(const EndpointPtr & ep, const IObjectOutputStreamPtr &outputStream, int timeout); + + void ReadControl(u8 type, u8 req, u16 value, u16 index, ByteArray &data, int timeout); + void WriteControl(u8 type, u8 req, u16 value, u16 index, const ByteArray &data, int timeout); + + void ClearHalt(const EndpointPtr & ep); + + std::string GetString(int idx) const; + }; + DECLARE_PTR(Device); +}} + +#endif /* DEVICE_H */ + diff -Nru android-file-transfer-4.2/mtp/backend/haiku/usb/DeviceDescriptor.cpp android-file-transfer-4.3/mtp/backend/haiku/usb/DeviceDescriptor.cpp --- android-file-transfer-4.2/mtp/backend/haiku/usb/DeviceDescriptor.cpp 1970-01-01 00:00:00.000000000 +0000 +++ android-file-transfer-4.3/mtp/backend/haiku/usb/DeviceDescriptor.cpp 2023-12-12 22:22:35.000000000 +0000 @@ -0,0 +1,43 @@ +/* + * DeviceDescriptor.cpp + * Copyright (C) 2022 pulkomandy + * + * Distributed under terms of the MIT license. + */ + +#include "DeviceDescriptor.h" + + + +namespace mtp { namespace usb +{ + DeviceDescriptor::DeviceDescriptor(BUSBDevice* dev) + :_dev(dev) + { + } + + DeviceDescriptor::~DeviceDescriptor() + { + } + + ConfigurationPtr DeviceDescriptor::GetConfiguration(int conf) + { + return std::make_shared(_dev->ConfigurationAt(conf)); + } + + ByteArray DeviceDescriptor::GetDescriptor() const + { + const usb_device_descriptor* descriptor = _dev->Descriptor(); + ByteArray out; + out.reserve(sizeof(usb_device_descriptor)); + memcpy(out.data(), descriptor, sizeof(usb_device_descriptor)); + return out; + } + + DevicePtr DeviceDescriptor::TryOpen(ContextPtr context) + { + if (_dev->InitCheck() != B_OK) + return nullptr; + return std::make_shared(context, _dev); + } +}} diff -Nru android-file-transfer-4.2/mtp/backend/haiku/usb/DeviceDescriptor.h android-file-transfer-4.3/mtp/backend/haiku/usb/DeviceDescriptor.h --- android-file-transfer-4.2/mtp/backend/haiku/usb/DeviceDescriptor.h 1970-01-01 00:00:00.000000000 +0000 +++ android-file-transfer-4.3/mtp/backend/haiku/usb/DeviceDescriptor.h 2023-12-12 22:22:35.000000000 +0000 @@ -0,0 +1,81 @@ +/* + This file is part of Android File Transfer For Linux. + Copyright (C) 2015-2020 Vladimir Menshakov + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef AFTL_MTP_BACKEND_LIBUSB_USB_DEVICEDESCRIPTOR_H +#define AFTL_MTP_BACKEND_LIBUSB_USB_DEVICEDESCRIPTOR_H + +#include +#include +#include + +namespace mtp { namespace usb +{ + class Configuration : Noncopyable + { + const BUSBConfiguration *_config; + + public: + Configuration(const BUSBConfiguration *config) : _config(config) { } + ~Configuration() { /* configuration is owned by its parent device */ } + + int GetIndex() const + { return _config->Index(); } + + int GetInterfaceCount() const + { return _config->CountInterfaces(); } + + int GetInterfaceAltSettingsCount(int idx) const + { return _config->InterfaceAt(idx)->CountAlternates(); } + + // TODO figure out what to do with "int settings" + InterfacePtr GetInterface(DevicePtr device, ConfigurationPtr config, int idx, int settings) const + { return std::make_shared(device, config, *_config->InterfaceAt(idx)->AlternateAt(settings)); } + }; + + class DeviceDescriptor + { + private: + BUSBDevice * _dev; + + public: + DeviceDescriptor(BUSBDevice *dev); + ~DeviceDescriptor(); + + u16 GetVendorId() const + { return _dev->VendorID(); } + + u16 GetProductId() const + { return _dev->ProductID(); } + + DevicePtr Open(ContextPtr context); + DevicePtr TryOpen(ContextPtr context); + + int GetConfigurationsCount() const + { return _dev->CountConfigurations(); } + + ConfigurationPtr GetConfiguration(int conf); + + ByteArray GetDescriptor() const; + }; + DECLARE_PTR(DeviceDescriptor); + +}} + +#endif + diff -Nru android-file-transfer-4.2/mtp/backend/haiku/usb/Interface.h android-file-transfer-4.3/mtp/backend/haiku/usb/Interface.h --- android-file-transfer-4.2/mtp/backend/haiku/usb/Interface.h 1970-01-01 00:00:00.000000000 +0000 +++ android-file-transfer-4.3/mtp/backend/haiku/usb/Interface.h 2023-12-12 22:22:35.000000000 +0000 @@ -0,0 +1,74 @@ +/* + This file is part of Android File Transfer For Linux. + Copyright (C) 2015-2020 Vladimir Menshakov + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef AFTL_MTP_BACKEND_LIBUSB_USB_INTERFACE_H +#define AFTL_MTP_BACKEND_LIBUSB_USB_INTERFACE_H + +#include +#include + +namespace mtp { namespace usb +{ + + class Configuration; + DECLARE_PTR(Configuration); + + class InterfaceToken : public IToken + { + BUSBDevice * _handle; + int _index; + + public: + InterfaceToken(BUSBDevice *handle, int index); + ~InterfaceToken(); + }; + DECLARE_PTR(InterfaceToken); + + class Interface + { + DevicePtr _device; + ConfigurationPtr _config; + const BUSBInterface & _interface; + + public: + Interface(DevicePtr device, ConfigurationPtr config, const BUSBInterface &interface): _device(device), _config(config), _interface(interface) { } + + u8 GetClass() const + { return _interface.Class(); } + + u8 GetSubclass() const + { return _interface.Subclass(); } + + int GetIndex() const + { return _interface.Index(); } + + EndpointPtr GetEndpoint(int idx) const + { return std::make_shared(*_interface.EndpointAt(idx)); } + + int GetEndpointsCount() const + { return _interface.CountEndpoints(); } + + std::string GetName() const + { return _interface.InterfaceString(); } + }; + DECLARE_PTR(Interface); + +}} + +#endif diff -Nru android-file-transfer-4.2/mtp/backend/linux/usb/Context.cpp android-file-transfer-4.3/mtp/backend/linux/usb/Context.cpp --- android-file-transfer-4.2/mtp/backend/linux/usb/Context.cpp 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/mtp/backend/linux/usb/Context.cpp 2023-12-12 22:22:35.000000000 +0000 @@ -41,7 +41,7 @@ { unsigned busId, conf, interface; char portBuf[256]; - if (sscanf(entry.c_str(), "%u-%256[0-9.]:%u.%u", &busId, portBuf, &conf, &interface) == 4) + if (sscanf(entry.c_str(), "%u-%255[0-9.]:%u.%u", &busId, portBuf, &conf, &interface) == 4) { std::string port = portBuf; if ((port.size() == 1 && port[0] == '0')) diff -Nru android-file-transfer-4.2/mtp/ptp/Device.cpp android-file-transfer-4.3/mtp/ptp/Device.cpp --- android-file-transfer-4.2/mtp/ptp/Device.cpp 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/mtp/ptp/Device.cpp 2023-12-12 22:22:35.000000000 +0000 @@ -181,9 +181,11 @@ DevicePtr Device::FindFirst(usb::ContextPtr ctx, const std::string & filter, bool claimInterface, bool resetDevice) { int vendor, product; + bool useUsbVendorProduct = true; if (sscanf(filter.c_str(), "%x:%x", &vendor, &product) != 2) { vendor = product = -1; + useUsbVendorProduct = false; } for (usb::DeviceDescriptorPtr desc : ctx->GetDevices()) @@ -196,7 +198,7 @@ } auto device = Open(ctx, desc, claimInterface, resetDevice); - if (device && device->Matches(filter)) + if (device && (useUsbVendorProduct || device->Matches(filter))) return device; } catch(const std::exception &ex) diff -Nru android-file-transfer-4.2/mtp/ptp/ObjectFormat.cpp android-file-transfer-4.3/mtp/ptp/ObjectFormat.cpp --- android-file-transfer-4.2/mtp/ptp/ObjectFormat.cpp 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/mtp/ptp/ObjectFormat.cpp 2023-12-12 22:22:35.000000000 +0000 @@ -175,6 +175,7 @@ time_t ConvertDateTime(const std::string ×pec) { struct tm time = {}; + time.tm_isdst = -1; char *end = strptime(timespec.c_str(), "%Y%m%dT%H%M%S", &time); if (!end) return 0; diff -Nru android-file-transfer-4.2/mtp/types.h android-file-transfer-4.3/mtp/types.h --- android-file-transfer-4.2/mtp/types.h 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/mtp/types.h 2023-12-12 22:22:35.000000000 +0000 @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace mtp diff -Nru android-file-transfer-4.2/python/aftl.cpp android-file-transfer-4.3/python/aftl.cpp --- android-file-transfer-4.2/python/aftl.cpp 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/python/aftl.cpp 2023-12-12 22:22:35.000000000 +0000 @@ -152,9 +152,15 @@ ; ; + py::class_(m, "UsbContext"). + def(py::init<>()). + def("get_device_descriptors", &usb::Context::GetDevices) + ; + py::class_(m, "Device"). def_static("find_first", static_cast(&Device::FindFirst), py::arg("filter_device") = std::string(), py::arg("claim_interface") = true, py::arg("reset_device") = false). + def_static("open", &Device::Open, py::arg("context"), py::arg("device_descriptor"), py::arg("claim_interface") = true, py::arg("reset_device") = false). def("open_session", &Device::OpenSession, py::arg("session_id") = 1, py::arg("timeout") = static_cast(Session::DefaultTimeout)) ; diff -Nru android-file-transfer-4.2/qt/CMakeLists.txt android-file-transfer-4.3/qt/CMakeLists.txt --- android-file-transfer-4.2/qt/CMakeLists.txt 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/qt/CMakeLists.txt 2023-12-12 22:22:35.000000000 +0000 @@ -50,6 +50,7 @@ translations/android-file-transfer-linux_it.ts translations/android-file-transfer-linux_nl.ts translations/android-file-transfer-linux_ru.ts + translations/android-file-transfer-linux_zh-CN.ts ) if (Qt5Widgets_FOUND) diff -Nru android-file-transfer-4.2/qt/android-file-transfer.appdata.xml android-file-transfer-4.3/qt/android-file-transfer.appdata.xml --- android-file-transfer-4.2/qt/android-file-transfer.appdata.xml 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/qt/android-file-transfer.appdata.xml 2023-12-12 22:22:35.000000000 +0000 @@ -6,18 +6,20 @@ Android File Transfer Interactive MTP client with Qt4/Qt5 GUI -

-Simple Qt UI with progress dialogs. - -FUSE wrapper (If you'd prefer mounting your device), - supporting partial read/writes, allowing instant access to your files. - -No file size limits. - -Automatically renames album cover to make it visible from media player. - -No extra dependencies (e.g. libptp/libmtp). - -Available as static/shared library. - -Simple CLI tool.

+
    +
  • Simple Qt UI with progress dialogs.
  • +
  • FUSE wrapper (If you'd prefer mounting your device), + supporting partial read/writes, allowing instant access to your files.
  • +
  • No file size limits.
  • +
  • Automatically renames album cover to make it visible from media player.
  • +
  • No extra dependencies (e.g. libptp/libmtp).
  • +
  • Available as static/shared library.
  • +
  • Simple CLI tool.
  • +
android-file-transfer.desktop https://whoozle.github.io/android-file-transfer-linux - vladimir.menshakov@gmail.com + vladimir.menshakov@gmail.com android-file-transfer.desktop diff -Nru android-file-transfer-4.2/qt/devicesdialog.h android-file-transfer-4.3/qt/devicesdialog.h --- android-file-transfer-4.2/qt/devicesdialog.h 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/qt/devicesdialog.h 2023-12-12 22:22:35.000000000 +0000 @@ -32,7 +32,7 @@ mtp::DevicePtr getDevice(); - int exec() override; + int exec(); private slots: void scan(); diff -Nru android-file-transfer-4.2/qt/mainwindow.cpp android-file-transfer-4.3/qt/mainwindow.cpp --- android-file-transfer-4.2/qt/mainwindow.cpp 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/qt/mainwindow.cpp 2023-12-12 22:22:35.000000000 +0000 @@ -231,9 +231,8 @@ { DevicesDialog dialog(_resetDevice, this); int r = dialog.exec(); - if (r == QDialog::Rejected) - return false; - _device = dialog.getDevice(); + if (r == QDialog::Accepted) + _device = dialog.getDevice(); } if (!_device) diff -Nru android-file-transfer-4.2/qt/translations/android-file-transfer-linux_cs.ts android-file-transfer-4.3/qt/translations/android-file-transfer-linux_cs.ts --- android-file-transfer-4.2/qt/translations/android-file-transfer-linux_cs.ts 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/qt/translations/android-file-transfer-linux_cs.ts 2023-12-12 22:22:35.000000000 +0000 @@ -53,6 +53,59 @@ + DevicesDialog + + + Dialog + Dialog + + + + Rescan Devices + + + + + Kill Users + + + + + The following processes are keeping file descriptors for your device: + + Následující procesy drží popisovače souboru pro vaše zařízení: + + + + + Device is busy + Zařízení je zaneprázdněno + + + + Device is busy, maybe another process is using it. + + + Zařízení je zaneprázdněno, možná jej používá jiný proces. + + + + + + Close other MTP applications and restart Android File Transfer. + +Press Abort to kill them or Ignore to try next device. + Zavřete ostatní MTP aplikace a restartujte program. + +Zmáčkněte Zrušit pro jejich ukončení nebo Ignorovat pro použití dalšího zařízení. + + + + USB Device %1:%2 + + + + MainWindow @@ -202,8 +255,8 @@ - - + + Attach Cover Vložit Obal @@ -272,63 +325,57 @@ Vyjímka: %1 - The following processes are keeping file descriptors for your device: - Následující procesy drží popisovače souboru pro vaše zařízení: + Následující procesy drží popisovače souboru pro vaše zařízení: - Device is busy - Zařízení je zaneprázdněno + Zařízení je zaneprázdněno - Device is busy, maybe another process is using it. - Zařízení je zaneprázdněno, možná jej používá jiný proces. + Zařízení je zaneprázdněno, možná jej používá jiný proces. - Close other MTP applications and restart Android File Transfer. Press Abort to kill them or Ignore to try next device. - Zavřete ostatní MTP aplikace a restartujte program. + Zavřete ostatní MTP aplikace a restartujte program. Zmáčkněte Zrušit pro jejich ukončení nebo Ignorovat pro použití dalšího zařízení. - Device::Find failed - Zařízení::Hledání selhalo + Zařízení::Hledání selhalo - MTP device could not be opened at the moment Failure: %1 Press Reset to reset them or Ignore to try next device. - MTP zařízení nemohlo být nyní otevřeno + MTP zařízení nemohlo být nyní otevřeno Problém: %1 Zmáčkněte Reset pro obnovu nebo Ignorovat pro použití dalšího zařízení. - + No MTP device found Nenalezeno žádné MTP zařízení - + MTPZ Keys are Missing MTPZ Klíče Nenalezeny - + It seems your computer is missing an important bit for talking with MTPZ device: private keys and certificate material from Microsoft. This means that you can't download and upload files from/to such devices. @@ -347,7 +394,7 @@ - + Alternatively I (as an app) can offer you to download keys from the Internet. Can I download keys for you? @@ -358,92 +405,92 @@ (Zmáčkněte Ano jen v případě, že je řečené u vás legální nebo je vám to jedno). - + You can look for .mtpz-data file on the internet, download it and place it in your home directory. Můžete najít .mtpz-data soubor na internetu a stáhnou jej do vaší domovské složky. - - + + MTP MTP - + MTP device does not respond MTP zařízení neodpovídá - + Could not open MTP session: %1 Nemohu otevřít MTP sezení: %1 - + No MTP Storages Žádné MTP Uložiště - + No MTP storage found, your device might be locked. Please unlock and press Retry to continue or Abort to exit. Nenalezeno žádné MTP uložiště, vaše zařízení může být zamčeno. Prosím odemčete jej a zmáčknětě Zkust znovu pro pokračování nebo Zrušit pro ukončení. - + Loading Media Library Načítání Knihovny Médií - + Deleting file(s) Mazání souboru(ů) - + Are you sure? Opravdu? - + Error Chyba - + Failed to create new directory: Vytvoření nové složky selhalo: - + Download Progress Postup Stahování - + Importing Progress Postup Importu - + Overwrite confirmation Potvrzení přepsání - + You are about to overwrite file Chystáte se přepsat soubor - + Could not open selected file Nemohu otevřít vybraný soubor - + Could not attach cover: %1 Nemohu vložit obal: %1 diff -Nru android-file-transfer-4.2/qt/translations/android-file-transfer-linux_it.ts android-file-transfer-4.3/qt/translations/android-file-transfer-linux_it.ts --- android-file-transfer-4.2/qt/translations/android-file-transfer-linux_it.ts 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/qt/translations/android-file-transfer-linux_it.ts 2023-12-12 22:22:35.000000000 +0000 @@ -2,6 +2,44 @@ + CommandQueue + + + Loading media library… + + + + + Querying artists… + + + + + Loading artists… + + + + + Querying albums… + + + + + Loading albums… + + + + + Done + + + + + Setting cover for album %1 + + + + CreateDirectoryDialog @@ -15,6 +53,54 @@ + DevicesDialog + + + Dialog + Dialogo + + + + Rescan Devices + + + + + Kill Users + + + + + The following processes are keeping file descriptors for your device: + + + + + + Device is busy + Dispositivo è occupato + + + + Device is busy, maybe another process is using it. + + + + + + + Close other MTP applications and restart Android File Transfer. + +Press Abort to kill them or Ignore to try next device. + + + + + USB Device %1:%2 + + + + MainWindow @@ -32,189 +118,328 @@ &Navigare - + Upload Caricare - + Ctrl+U Ctrl+U - + &CreateDirectory &CreaDirectory - + Ctrl+N Ctrl+N - + UploadDirectory Carica Directory - + &Back &Indietro - + Backspace BACKSPACE - + &Go Down &Scendere - + Return Ritornare - + Upload Album Carica Album - + &Rename &Rinomina - + F2 F2 - + D&elete &Cancella - + Del Del - + &Download &Scarica - + Ctrl+D Ctrl+D - + E&xit U&scita - + Ctrl+Q Ctrl+Q - + Re&fresh &Aggiorna - + F5 F5 - + &Paste &Incolla + + Show Thumbnails + + + + + Show Thumbnails of images where available + + + + + + Import Music + + + + + Remove Cover + + + + + + + Attach Cover + + + + + Import Music Files + + + + + Import individual music files + + + Paste - Incolla + Incolla - + Ctrl+V Ctrl+V - Device is busy - Dispositivo è occupato + Dispositivo è occupato - Device is busy, maybe another process is using it. Close other MTP applications and restart Android File Transfer. - Dispositivo è occupato, forse un altro processo lo sta usando. + Dispositivo è occupato, forse un altro processo lo sta usando. Chiudere altre applicazioni MTP e riavviare Android File Transfer. - + + MTPZ Keys Download + + + + + Could not download keys, please find the error below: + +%1 + +Please look for .mtpz-data file on the internet and manually install it to your home directory. + + + + + Could not write keys to %1 + + + + + Could not write keys, please find the error below: + +%1 + +Please look for .mtpz-data file on the internet and manually install it to your home directory. + + + + + MTPZ keys have been installed to your system. + + + + + Your MTPZ keys failed to install or load. + +Please restart the application to try again. + +Exception: %1 + + + + No MTP device found Nessun dispositivo MTP trovato - + + MTPZ Keys are Missing + + + + + It seems your computer is missing an important bit for talking with MTPZ device: private keys and certificate material from Microsoft. +This means that you can't download and upload files from/to such devices. + +Microsoft could have released that key material and documentation for MTPZ devices as they are not interested in those anymore. + +Because of the legal risks we can't bundle those keys, even though in some countries it's lawful to modify things to make them working again, just because you own it. + + + + + + + Alternatively I (as an app) can offer you to download keys from the Internet. +Can I download keys for you? + +(Please press Yes only if all of the above is legal in your country or you just don't care). + + + + + You can look for .mtpz-data file on the internet, download it and place it in your home directory. + + + + + MTP MTP - + MTP device does not respond Il dispositivo MTP non risponde - + + Could not open MTP session: %1 + + + + No MTP Storages Nessun Dispositivo MTP - + No MTP storage found, your device might be locked. Please unlock and press Retry to continue or Abort to exit. Nessun dispositivo MTP trovato, il dispositivo potrebbe essere bloccato. Si prega di sbloccare e premere Ritenta per continuare o Interrompere per uscire. - + + Loading Media Library + + + + Deleting file(s) Eliminazione di file - + Are you sure? Sie sicuri? - + Error Errore - + Failed to create new directory: Impossibile creare una nuova directory: - + + Download Progress + + + + + Importing Progress + + + + Overwrite confirmation Confermare la Sovrascrittura - + You are about to overwrite file Si sta per sovrascrivere file + + + Could not open selected file + + + + + Could not attach cover: %1 + + MtpStoragesModel @@ -232,26 +457,35 @@ Avanzamento Caricamento - - - Speed: - Velocità: + Velocità: - kB/s - kB/s + kB/s - MB/s - MB/s + MB/s - GB/s - GB/s + GB/s + + + + Speed: %1 kB/s + + + + + Speed: %1 MB/s + + + + + Speed: %1 GB/s + diff -Nru android-file-transfer-4.2/qt/translations/android-file-transfer-linux_nl.ts android-file-transfer-4.3/qt/translations/android-file-transfer-linux_nl.ts --- android-file-transfer-4.2/qt/translations/android-file-transfer-linux_nl.ts 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/qt/translations/android-file-transfer-linux_nl.ts 2023-12-12 22:22:35.000000000 +0000 @@ -4,29 +4,49 @@ CommandQueue - Loading media library… - Bezig met laden van mediaverzameling… + Bezig met laden van mediaverzameling… - Querying artists… - Bezig met zoeken naar artiesten… + Bezig met zoeken naar artiesten… - Loading artists… - Bezig met laden van artiesten… + Bezig met laden van artiesten… - Querying albums… - Bezig met zoeken naar albums… + Bezig met zoeken naar albums… - Loading albums… - Bezig met laden van albums… + Bezig met laden van albums… + + + + Loading media library… + + + + + Querying artists… + + + + + Loading artists… + + + + + Querying albums… + + + + + Loading albums… + @@ -53,6 +73,59 @@ + DevicesDialog + + + Dialog + Venster + + + + Rescan Devices + + + + + Kill Users + + + + + The following processes are keeping file descriptors for your device: + + De volgende processen bevatten bestandsomschrijvingen van je apparaat: + + + + + Device is busy + Apparaat is bezig + + + + Device is busy, maybe another process is using it. + + + Het apparaat is bezig. Mogelijk is het in gebruik door een ander proces. + + + + + + Close other MTP applications and restart Android File Transfer. + +Press Abort to kill them or Ignore to try next device. + Sluit andere mtp-programma's en herstart Android-bestandsoverdracht. + +Druk op 'Afbreken' om ze af te sluiten of 'Negeren' om een ander apparaat te proberen. + + + + USB Device %1:%2 + + + + MainWindow @@ -202,8 +275,8 @@ - - + + Attach Cover Hoes bijsluiten @@ -272,63 +345,57 @@ Uitzonderingsfout: %1 - The following processes are keeping file descriptors for your device: - De volgende processen bevatten bestandsomschrijvingen van je apparaat: + De volgende processen bevatten bestandsomschrijvingen van je apparaat: - Device is busy - Apparaat is bezig + Apparaat is bezig - Device is busy, maybe another process is using it. - Het apparaat is bezig. Mogelijk is het in gebruik door een ander proces. + Het apparaat is bezig. Mogelijk is het in gebruik door een ander proces. - Close other MTP applications and restart Android File Transfer. Press Abort to kill them or Ignore to try next device. - Sluit andere mtp-programma's en herstart Android-bestandsoverdracht. + Sluit andere mtp-programma's en herstart Android-bestandsoverdracht. Druk op 'Afbreken' om ze af te sluiten of 'Negeren' om een ander apparaat te proberen. - Device::Find failed - Apparaat niet aangetroffen + Apparaat niet aangetroffen - MTP device could not be opened at the moment Failure: %1 Press Reset to reset them or Ignore to try next device. - Het mtp-apparaat kan momenteel niet worden benaderd. + Het mtp-apparaat kan momenteel niet worden benaderd. Foutmelding: %1 Druk op 'Herstellen' om te herstellen of 'Negeren' om een ander apparaat te proberen. - + No MTP device found Geen mtp-apparaat aangetroffen - + MTPZ Keys are Missing De mtpz-sleutels ontbreken - + It seems your computer is missing an important bit for talking with MTPZ device: private keys and certificate material from Microsoft. This means that you can't download and upload files from/to such devices. @@ -347,7 +414,7 @@ - + Alternatively I (as an app) can offer you to download keys from the Internet. Can I download keys for you? @@ -358,92 +425,92 @@ (klik alleen op 'Ja' als dit legaal is in je land of als de wet je niet interesseert) - + You can look for .mtpz-data file on the internet, download it and place it in your home directory. Je kunt .mtpz-gegevens vinden op het internet en deze in je persoonlijke map plaatsen. - - + + MTP MTP - + MTP device does not respond MTP-apparaat reageert niet - + Could not open MTP session: %1 De mtp-sessie kan niet worden geopend: %1 - + No MTP Storages Geen mtp-apparaten - + No MTP storage found, your device might be locked. Please unlock and press Retry to continue or Abort to exit. Geen mtp-apparaat aangetroffen - wellicht is je apparaat vergrendeld. Ontgrendel je apparaat en druk op 'Opnieuw' of 'Afbreken'. - + Loading Media Library Bezig met laden van mediaverzameling - + Deleting file(s) Bezig met verwijderen van bestand(en) - + Are you sure? Weet je het zeker? - + Error Fout - + Failed to create new directory: De map kan niet worden aangemaakt: - + Download Progress Downloadvoortgang - + Importing Progress Importvoortgang - + Overwrite confirmation Overschrijven bevestigen - + You are about to overwrite file Je staat op het punt om het volgende te overschrijven: - + Could not open selected file Kan het geselecteerde bestand niet openen - + Could not attach cover: %1 De hoes kan niet worden bijgesloten: %1 diff -Nru android-file-transfer-4.2/qt/translations/android-file-transfer-linux_ru.ts android-file-transfer-4.3/qt/translations/android-file-transfer-linux_ru.ts --- android-file-transfer-4.2/qt/translations/android-file-transfer-linux_ru.ts 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/qt/translations/android-file-transfer-linux_ru.ts 2023-12-12 22:22:35.000000000 +0000 @@ -53,6 +53,59 @@ + DevicesDialog + + + Dialog + Диалог + + + + Rescan Devices + Сканировать Устройства + + + + Kill Users + Убить использующие процессы + + + + The following processes are keeping file descriptors for your device: + + Следующие процессы удерживают файловые дескрипторы вашего устройства: + + + + + Device is busy + Устройство занято + + + + Device is busy, maybe another process is using it. + + + Устройство занято, может быть какой-нибудь другой процесс его использует. + + + + + + Close other MTP applications and restart Android File Transfer. + +Press Abort to kill them or Ignore to try next device. + Закройте другие MTP приложения и перезапустите Android File Transfer + +Нажмите "Прервать" чтобы убить сторонний процесс, или "Игнорировать" чтобы попробовать следующее устройство. + + + + USB Device %1:%2 + Усб Устройство %1:%2 + + + MainWindow @@ -197,8 +250,8 @@ - - + + Attach Cover Установить обложку @@ -222,9 +275,8 @@ - Device is busy - Устройство занято + Устройство занято Device is busy, maybe another process is using it. Close other MTP applications and restart Android File Transfer. @@ -286,58 +338,53 @@ Ошибка: %1 - The following processes are keeping file descriptors for your device: - Следующие процессы удерживают файловые дескрипторы вашего устройства: + Следующие процессы удерживают файловые дескрипторы вашего устройства: - Device is busy, maybe another process is using it. - Устройство занято, может быть какой-нибудь другой процесс его использует. + Устройство занято, может быть какой-нибудь другой процесс его использует. - Close other MTP applications and restart Android File Transfer. Press Abort to kill them or Ignore to try next device. - Закройте другие MTP приложения и перезапустите Android File Transfer + Закройте другие MTP приложения и перезапустите Android File Transfer Нажмите "Прервать" чтобы убить сторонний процесс, или "Игнорировать" чтобы попробовать следующее устройство. - Device::Find failed - Device::Find взорвалось + Device::Find взорвалось - MTP device could not be opened at the moment Failure: %1 Press Reset to reset them or Ignore to try next device. - MTP устройство не может быть открыто в данный момент + MTP устройство не может быть открыто в данный момент Ошибка: %1 Нажмите "Сброс" чтобы попытаться сбросить устройство или "Игнорировать" чтобы попробовать другие устройства. - + No MTP device found Не найдено ни одного устройства MTP - + MTPZ Keys are Missing MTPZ ключи не найдены - + It seems your computer is missing an important bit for talking with MTPZ device: private keys and certificate material from Microsoft. This means that you can't download and upload files from/to such devices. @@ -356,7 +403,7 @@ - + Alternatively I (as an app) can offer you to download keys from the Internet. Can I download keys for you? @@ -367,92 +414,92 @@ Пожалуйста нажмите "Да" только если всё вышеуказанное легально в вашей стране. (Или вам всё равно) - + You can look for .mtpz-data file on the internet, download it and place it in your home directory. Вы можете найти файл ".mtpz-data" в интернете и просто положить его в домашнюю директорию. - - + + MTP - + MTP device does not respond MTP устройство не отвечает - + Could not open MTP session: %1 Не получается открыть MTP сессию: %1 - + No MTP Storages Нет хранилищ MTP - + No MTP storage found, your device might be locked. Please unlock and press Retry to continue or Abort to exit. Нет ни одного хранилища, возможно ваше устройство заблокировано Разблокируйте устройство и нажмите Повторить чтобы попробовать ещё раз или Прервать, чтобы выйти. - + Loading Media Library Загружаем библиотеку - + Deleting file(s) Удаление файла(ов) - + Are you sure? Вы уверены? - + Error Ошибка - + Failed to create new directory: Не удалось создать новый каталог: - + Download Progress Загрузка - + Importing Progress Импорт - + Overwrite confirmation Подтверждение перезаписи - + You are about to overwrite file Вы собираетесь перезаписать файл - + Could not open selected file Не получилось открыть выбранный файл - + Could not attach cover: %1 Не удалось установить обложку: %1 diff -Nru android-file-transfer-4.2/qt/translations/android-file-transfer-linux_zh-CN.ts android-file-transfer-4.3/qt/translations/android-file-transfer-linux_zh-CN.ts --- android-file-transfer-4.2/qt/translations/android-file-transfer-linux_zh-CN.ts 1970-01-01 00:00:00.000000000 +0000 +++ android-file-transfer-4.3/qt/translations/android-file-transfer-linux_zh-CN.ts 2023-12-12 22:22:35.000000000 +0000 @@ -0,0 +1,502 @@ + + + + + CommandQueue + + + Loading media library… + 正在加载媒体库…… + + + + Querying artists… + 正在查询艺术家…… + + + + Loading artists… + 正在加载艺术家…… + + + + Querying albums… + 正在查询相册…… + + + + Loading albums… + 正在加载相册…… + + + + Done + 已完成 + + + + Setting cover for album %1 + 为相册 %1 设定封面 + + + + CreateDirectoryDialog + + + Dialog + 新目录 + + + + Create New Directory + 创建新目录 + + + + DevicesDialog + + + Dialog + 设备行为 + + + + Rescan Devices + 重新扫描设备 + + + + Kill Users + 杀死用户 + + + + The following processes are keeping file descriptors for your device: + + 以下进程正在为您的设备保存文件描述符: + + + + + Device is busy + 设备正忙 + + + + Device is busy, maybe another process is using it. + + + 设备正忙,可能有另外的进程正在使用。 + + + + + + Close other MTP applications and restart Android File Transfer. + +Press Abort to kill them or Ignore to try next device. + 请关闭其他的 MTP 应用并重新启动 Android 文件传输器。 + +点击“放弃”以终止它们,或“忽略”以尝试下一台设备。 + + + + USB Device %1:%2 + USB 设备 %1:%2 + + + + MainWindow + + + Android File Transfer for Linux + Android 文件传输器(Linux 版) + + + + &File + 文件(&F) + + + + &Navigate + 导航(&N) + + + + Upload + 上传 + + + + Ctrl+U + Ctrl+U + + + + &CreateDirectory + 创建目录(&C) + + + + Ctrl+N + Ctrl+N + + + + UploadDirectory + 上传目录 + + + + &Back + 后退(&B) + + + + Backspace + Backspace + + + + &Go Down + 转入(&G) + + + + Return + Return + + + + Upload Album + 上传相册 + + + + &Rename + 重命名(&R) + + + + F2 + F2 + + + + D&elete + 删除(&E) + + + + Del + Del + + + + &Download + 下载(&D) + + + + Ctrl+D + Ctrl+D + + + + E&xit + 退出(&X) + + + + Ctrl+Q + Ctrl+Q + + + + Re&fresh + 刷新(&F) + + + + F5 + F5 + + + + &Paste + 粘贴(&P) + + + + Ctrl+V + Ctrl+V + + + + Show Thumbnails + 显示缩略图 + + + + Show Thumbnails of images where available + 显示图像的缩略图(如果可用) + + + + + Import Music + 导入音乐 + + + + Remove Cover + 移除封面 + + + + + + Attach Cover + 附加封面 + + + + Import Music Files + 导入音乐文件 + + + + Import individual music files + 导入个人音乐文件 + + + + MTPZ Keys Download + MTPZ 密钥下载 + + + + Could not download keys, please find the error below: + +%1 + +Please look for .mtpz-data file on the internet and manually install it to your home directory. + 无法下载密钥,请在下方找出错误: + +%1 + +请在互联网上寻找 .mtpz-data 文件并手动将其安装到家目录中。 + + + + Could not write keys to %1 + 无法将密钥写入 %1 + + + + Could not write keys, please find the error below: + +%1 + +Please look for .mtpz-data file on the internet and manually install it to your home directory. + 无法写入密钥,请在下方找出错误: + +%1 + +请在互联网上寻找 .mtpz-data 文件并手动将其安装到家目录中。 + + + + MTPZ keys have been installed to your system. + MTPZ 密钥已安装到系统。 + + + + Your MTPZ keys failed to install or load. + +Please restart the application to try again. + +Exception: %1 + MTPZ 密钥安装或加载失败。 + +请重启应用再试一次。 + +异常:%1 + + + + No MTP device found + 找不到 MTP 设备 + + + + MTPZ Keys are Missing + MTPZ 密钥缺失 + + + + It seems your computer is missing an important bit for talking with MTPZ device: private keys and certificate material from Microsoft. +This means that you can't download and upload files from/to such devices. + +Microsoft could have released that key material and documentation for MTPZ devices as they are not interested in those anymore. + +Because of the legal risks we can't bundle those keys, even though in some countries it's lawful to modify things to make them working again, just because you own it. + + + 计算机似乎缺少了与MTPZ设备对话的重要部分:来自微软(Microsoft)的私钥和证书材料。 +这意味着无法从这些设备下载和上传文件。 + +微软可能已经发布了 MTPZ 设备的密钥材料和文档,因为他们对这些不再感兴趣了。 + +尽管在一些国家,因个人持有文件而对其进行修改、使其重新工作的行为是合法的,但由于各种法律风险,我们无法捆绑这些密钥。 + + + + + + Alternatively I (as an app) can offer you to download keys from the Internet. +Can I download keys for you? + +(Please press Yes only if all of the above is legal in your country or you just don't care). + 此外,本应用可为您从互联网上下载密钥。 +可否下载? + +(仅当上述行为在您所在国家合法,或者您不在意时,才点击“是”)。 + + + + You can look for .mtpz-data file on the internet, download it and place it in your home directory. + 可在互联网上寻找 .mtpz-data 文件,下载并将其放入家目录中。 + + + + + MTP + MTP + + + + MTP device does not respond + MTP 设备没有回应 + + + + Could not open MTP session: %1 + 无法打开 MTP 会话:%1 + + + + No MTP Storages + 没有 MTP 存储空间 + + + + No MTP storage found, your device might be locked. +Please unlock and press Retry to continue or Abort to exit. + 找不到 MTP 存储空间,您的设备可能已被锁定。 +请解锁设备并点击“重试”以继续或“放弃”以退出。 + + + + Loading Media Library + 正在加载媒体库 + + + + Deleting file(s) + 正在删除文件 + + + + Are you sure? + 确定吗? + + + + Error + 错误 + + + + Failed to create new directory: + + 创建新目录失败: + + + + + Download Progress + 下载进程 + + + + Importing Progress + 导入进程 + + + + Overwrite confirmation + 覆盖确认 + + + + You are about to overwrite file + 即将覆盖文件 + + + + Could not open selected file + 无法打开选定文件 + + + + Could not attach cover: %1 + 无法附加封面:%1 + + + + MtpStoragesModel + + + All storages (BUGS, BEWARE) + 所有存储空间 (有 Bug,请留意) + + + + ProgressDialog + + + Upload Progress + 上传进程 + + + + Speed: %1 kB/s + 速率:%1 kB/s + + + + Speed: %1 MB/s + 速率:%1 MB/s + + + + Speed: %1 GB/s + 速率:%1 GB/s + + + + RenameDialog + + + Dialog + 重命名 + + + + Rename Object + 重命名对象 + + + diff -Nru android-file-transfer-4.2/snap/snapcraft.yaml android-file-transfer-4.3/snap/snapcraft.yaml --- android-file-transfer-4.2/snap/snapcraft.yaml 2020-12-29 16:10:28.000000000 +0000 +++ android-file-transfer-4.3/snap/snapcraft.yaml 1970-01-01 00:00:00.000000000 +0000 @@ -1,99 +0,0 @@ - - # After registering a name on build.snapcraft.io, commit an uncommented line: - name: android-file-transfer-linux - version: '4.0+git' # just for humans, typically '1.2+git' or '1.3.2' - summary: Simple and reliable MTP client - description: | - Simple UI + Fuse wrapper + cli tool for exploring, downloading, uploading - data from/to your MTP device. Git version has experimental Zune support. - - grade: devel # must be 'stable' to release into candidate/stable channels - confinement: devmode # use 'strict' once you have the right plugs and slots - base: core18 - - apps: - android-file-transfer: - command: bin/desktop-launch android-file-transfer - desktop: share/applications/android-file-transfer.desktop - plugs: [x11, network, network-bind, desktop, gsettings, wayland] - environment: - # Use GTK3 cursor theme, icon theme and open/save file dialogs. - QT_QPA_PLATFORMTHEME: gtk3 - - aft-mtp-cli: - command: bin/aft-mtp-cli - aft-mtp-mount: - command: bin/aft-mtp-mount - - parts: - desktop-qt5: - source: https://github.com/ubuntu/snapcraft-desktop-helpers.git - source-subdir: qt - plugin: make - make-parameters: ["FLAVOR=qt5"] - build-packages: - - build-essential - - qtbase5-dev - - dpkg-dev - stage-packages: - - libxkbcommon0 - - ttf-ubuntu-font-family - - dmz-cursor-theme - - light-themes - - adwaita-icon-theme - - gnome-themes-standard - - shared-mime-info - - libqt5gui5 - - qtwayland5 - - libgdk-pixbuf2.0-0 - - libqt5svg5 # for loading icon themes which are svg - - try: [appmenu-qt5] # not available on core18 - - locales-all - - xdg-user-dirs - - fcitx-frontend-qt5 - - aftl: - # See 'snapcraft plugins' - source: . - after: [desktop-qt5] - plugin: cmake - - configflags: - - -DCMAKE_BUILD_TYPE=Release - - build-packages: - - qt5-default - - qttools5-dev - - qttools5-dev-tools - - libfuse-dev - - libmagic-dev - - libtag1-dev - - libssl-dev - - libreadline-dev - - pkg-config - - stage-packages: - - libqt5gui5 - - libqt5widgets5 - - libqt5network5 - - libfuse-dev - - libmagic-dev - - libtag1-dev - - libssl-dev - - libreadline-dev - - plugs: - # Support for common GTK themes - # https://forum.snapcraft.io/t/how-to-use-the-system-gtk-theme-via-the-gtk-common-themes-snap/6235 - gtk-3-themes: - interface: content - target: $SNAP/data-dir/themes - default-provider: gtk-common-themes - icon-themes: - interface: content - target: $SNAP/data-dir/icons - default-provider: gtk-common-themes - sound-themes: - interface: content - target: $SNAP/data-dir/sounds - default-provider: gtk-common-themes