Last active
October 14, 2025 16:22
-
-
Save niw/9e651b49d020b8dcf4b68e1efe566a1d to your computer and use it in GitHub Desktop.
Copy files to the "documents" folder by using MTP.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copy files to the "documents" folder by using MTP. | |
| # Mostly for sending documents locally to recent Kindle devices. | |
| # | |
| # Usage: python3 copy_to_documents.py [file ...] | |
| # | |
| # Requires `aftl` package that can be installed from a non-master branch at | |
| # <https://github.com/niw/android-file-transfer-linux>. | |
| # | |
| # Copyright (c) 2025 Yoshimasa Niwa | |
| # | |
| # Permission is hereby granted, free of charge, to any person obtaining | |
| # a copy of this software and associated documentation files (the | |
| # "Software"), to deal in the Software without restriction, including | |
| # without limitation the rights to use, copy, modify, merge, publish, | |
| # distribute, sublicense, and/or sell copies of the Software, and to | |
| # permit persons to whom the Software is furnished to do so, subject to | |
| # the following conditions: | |
| # | |
| # The above copyright notice and this permission notice shall be | |
| # included in all copies or substantial portions of the Software. | |
| # | |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| import argparse | |
| import os | |
| import aftl | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("files", nargs="*") | |
| args = parser.parse_args() | |
| device = aftl.Device.find_first() | |
| if device is None: | |
| print("No device found") | |
| exit(1) | |
| session = device.open_session() | |
| storage_ids = session.get_storage_ids() | |
| if not storage_ids: | |
| print("No storage found") | |
| exit(1) | |
| storage_id = storage_ids[0] | |
| info = session.get_storage_info(storage_id) | |
| name = info.StorageDescription | |
| print(f"Use storage: {name}") | |
| def get_child_object_ids( | |
| *, parent: aftl.ObjectId = aftl.Session.Root | |
| ) -> list[aftl.ObjectId]: | |
| return session.get_object_handles(storage_id, aftl.ObjectFormat.Any, parent) | |
| root_objects_ids = get_child_object_ids() | |
| documents_object_id: aftl.ObjectId | None = None | |
| for object_id in root_objects_ids: | |
| info = session.get_object_info(object_id) | |
| if ( | |
| info.Filename == "documents" | |
| and info.ObjectFormat == aftl.ObjectFormat.Association | |
| ): | |
| documents_object_id = object_id | |
| break | |
| if documents_object_id is None: | |
| print("No 'documents' folder found") | |
| exit(1) | |
| document_object_ids = get_child_object_ids(parent=documents_object_id) | |
| document_objects: dict[str, aftl.ObjectId] = {} | |
| for object_id in document_object_ids: | |
| info = session.get_object_info(object_id) | |
| document_objects[info.Filename] = object_id | |
| def upload_file(*, path: str, parent: aftl.ObjectId) -> aftl.ObjectId: | |
| filename = os.path.basename(path) | |
| size = os.path.getsize(path) | |
| object_info = aftl.ObjectInfo() | |
| object_info.Filename = filename | |
| object_info.ObjectFormat = aftl.ObjectFormat.Undefined | |
| object_info.ObjectCompressedSize = size | |
| new_object_info = session.send_object_info(object_info, storage_id, parent) | |
| with open(path, "rb") as f: | |
| session.send_object(f, size) | |
| return new_object_info.object_id | |
| for file in args.files: | |
| if not os.path.exists(file): | |
| continue | |
| filename = os.path.basename(file) | |
| object_id = document_objects.get(filename) | |
| if object_id: | |
| print(f"Found '{filename}' (object_id = {object_id})") | |
| print(f"Delete '{filename}' ...") | |
| session.delete_object(object_id) | |
| else: | |
| print(f"Not found '{filename}'") | |
| print(f"Upload '{filename}'...") | |
| upload_file(path=file, parent=documents_object_id) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment