Add direct Android MTP transport foundation
This commit is contained in:
parent
2d47805431
commit
c76d4c2106
17 changed files with 2641 additions and 0 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -123,3 +123,19 @@ Next:
|
||||||
- added direct exercise creation from the in-session exercise chooser;
|
- added direct exercise creation from the in-session exercise chooser;
|
||||||
- kept the final rolling 12-month `MM/YY` dashboard label inside its frame.
|
- kept the final rolling 12-month `MM/YY` dashboard label inside its frame.
|
||||||
<!-- TRAINLOG_EDITABILITY_NAV_CHANGELOG _END -->
|
<!-- TRAINLOG_EDITABILITY_NAV_CHANGELOG _END -->
|
||||||
|
|
||||||
|
<!-- TRAINLOG_MTP_TRANSPORT_CHANGELOG -->
|
||||||
|
### Direct Android USB/MTP transport foundation
|
||||||
|
|
||||||
|
- added `libudev` discovery of physical MTP devices;
|
||||||
|
- filtered MTP USB interface children to avoid duplicate phones;
|
||||||
|
- added exact bus/device matching into `libmtp`;
|
||||||
|
- added MTP storage enumeration;
|
||||||
|
- added root-folder discovery/creation;
|
||||||
|
- added direct MTP text-file upload;
|
||||||
|
- added direct folder-child listing;
|
||||||
|
- added direct MTP file download;
|
||||||
|
- split cached and uncached libmtp open paths where required;
|
||||||
|
- validated a real Android internal-storage write/list/read roundtrip;
|
||||||
|
- kept Android access mount-free: no GVFS/FUSE mount lifecycle is required.
|
||||||
|
<!-- TRAINLOG_MTP_TRANSPORT_CHANGELOG _END -->
|
||||||
|
|
|
||||||
|
|
@ -205,3 +205,32 @@ A valid Android export can still conflict with an independently edited TUI catal
|
||||||
The TUI owns final reconciliation.
|
The TUI owns final reconciliation.
|
||||||
|
|
||||||
Android must not assume that a matching display name means two different IDs may be silently merged.
|
Android must not assume that a matching display name means two different IDs may be silently merged.
|
||||||
|
|
||||||
|
<!-- TRAINLOG_ANDROID_MTP_TRANSPORT -->
|
||||||
|
## 15. USB file-transfer transport
|
||||||
|
|
||||||
|
The initial Android/Linux integration uses standard Android **file transfer
|
||||||
|
(MTP)** mode.
|
||||||
|
|
||||||
|
The Linux TUI/core accesses the device directly with `libudev` + `libmtp`.
|
||||||
|
Trainlog does not require a mounted Android filesystem.
|
||||||
|
|
||||||
|
Validated Linux-side capabilities:
|
||||||
|
|
||||||
|
```text
|
||||||
|
USB_MTP_DETECTION=PASS
|
||||||
|
MTP_STORAGE_ACCESS=PASS
|
||||||
|
MTP_WRITE=PASS
|
||||||
|
MTP_READ=PASS
|
||||||
|
MTP_ROUNDTRIP=PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
The transport foundation currently uses a root `Trainlog` folder for physical
|
||||||
|
validation.
|
||||||
|
|
||||||
|
The final JSON exchange subdirectory/naming convention is defined in the next
|
||||||
|
slice before the Android recorder depends on it.
|
||||||
|
|
||||||
|
The Android application itself will only need to produce a valid frozen
|
||||||
|
Trainlog JSON v1 document and place it in the agreed exchange area.
|
||||||
|
<!-- TRAINLOG_ANDROID_MTP_TRANSPORT _END -->
|
||||||
|
|
|
||||||
|
|
@ -138,3 +138,35 @@ Examples:
|
||||||
- duplicate session: report already imported, do not duplicate;
|
- duplicate session: report already imported, do not duplicate;
|
||||||
- unknown exercise: import it when valid catalog data is present;
|
- unknown exercise: import it when valid catalog data is present;
|
||||||
- incomplete active session: preserve it explicitly rather than silently inventing an end time.
|
- incomplete active session: preserve it explicitly rather than silently inventing an end time.
|
||||||
|
|
||||||
|
<!-- TRAINLOG_DIRECT_MTP_ARCHITECTURE -->
|
||||||
|
## 7. Direct Android USB/MTP transport
|
||||||
|
|
||||||
|
The Linux side does not require the Android device to be mounted as a normal
|
||||||
|
filesystem.
|
||||||
|
|
||||||
|
Transport layering is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Android USB file-transfer mode
|
||||||
|
|
|
||||||
|
v
|
||||||
|
libudev physical-device discovery
|
||||||
|
|
|
||||||
|
| bus number + device number
|
||||||
|
v
|
||||||
|
libmtp exact raw-device open
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Android internal MTP storage
|
||||||
|
```
|
||||||
|
|
||||||
|
This avoids GVFS/FUSE mount state and manual mount/unmount lifecycle management.
|
||||||
|
|
||||||
|
`libudev` owns physical-device discovery. `libmtp` owns storage and object
|
||||||
|
operations. The JSON exchange layer remains above both and stays independent
|
||||||
|
from USB/MTP backend details.
|
||||||
|
|
||||||
|
Current transport foundation supports folder creation, file upload, folder
|
||||||
|
listing, file download, and verified byte-for-byte roundtrip.
|
||||||
|
<!-- TRAINLOG_DIRECT_MTP_ARCHITECTURE _END -->
|
||||||
|
|
|
||||||
112
docs/reviews/mtp_transport.md
Normal file
112
docs/reviews/mtp_transport.md
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
# Direct USB/MTP transport checkpoint
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
```text
|
||||||
|
USB_MTP_DETECTION=PASS
|
||||||
|
MTP_STORAGE_ACCESS=PASS
|
||||||
|
MTP_ROOT_FOLDER_ACCESS=PASS
|
||||||
|
MTP_WRITE=PASS
|
||||||
|
MTP_LIST_FOLDER=PASS
|
||||||
|
MTP_READ=PASS
|
||||||
|
MTP_ROUNDTRIP=PASS
|
||||||
|
MTP_TRANSPORT_FOUNDATION=PASS
|
||||||
|
JSON_V1_MTP_TRANSFER=NEXT
|
||||||
|
TRAINLOG_FORMAT_V1=FROZEN
|
||||||
|
```
|
||||||
|
|
||||||
|
Trainlog now accesses Android file-transfer mode directly through Linux USB/MTP.
|
||||||
|
No GVFS mount, FUSE mount, file-manager mount, or manual mount/unmount cycle is
|
||||||
|
required.
|
||||||
|
|
||||||
|
## Detection
|
||||||
|
|
||||||
|
The discovery layer uses `libudev`.
|
||||||
|
|
||||||
|
Only physical USB devices with:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DEVTYPE=usb_device
|
||||||
|
ID_MTP_DEVICE=1
|
||||||
|
```
|
||||||
|
|
||||||
|
are returned.
|
||||||
|
|
||||||
|
USB interface children such as `6-1:1.0` are deliberately ignored so one
|
||||||
|
physical phone is never reported several times.
|
||||||
|
|
||||||
|
The device record contains:
|
||||||
|
|
||||||
|
- USB bus number;
|
||||||
|
- USB device number;
|
||||||
|
- vendor/product IDs;
|
||||||
|
- vendor/model strings when available;
|
||||||
|
- serial number when available;
|
||||||
|
- sysfs path.
|
||||||
|
|
||||||
|
## MTP access
|
||||||
|
|
||||||
|
The transport layer uses `libmtp`.
|
||||||
|
|
||||||
|
The udev bus/device pair is matched against libmtp raw devices so Trainlog opens
|
||||||
|
the exact physical phone selected by discovery.
|
||||||
|
|
||||||
|
Implemented operations:
|
||||||
|
|
||||||
|
- enumerate MTP storage areas;
|
||||||
|
- expose capacity and free-space information;
|
||||||
|
- find or create a root folder;
|
||||||
|
- upload a local text file;
|
||||||
|
- list direct children of one MTP folder;
|
||||||
|
- download one MTP file to a local path.
|
||||||
|
|
||||||
|
Cached and uncached libmtp opening modes are kept separate because folder-tree
|
||||||
|
operations and direct `LIBMTP_Get_Files_And_Folders()` traversal have different
|
||||||
|
backend requirements.
|
||||||
|
|
||||||
|
## Physical validation
|
||||||
|
|
||||||
|
The checkpoint was validated against an Android Samsung device in USB
|
||||||
|
"file transfer" / MTP mode.
|
||||||
|
|
||||||
|
Observed storage:
|
||||||
|
|
||||||
|
```text
|
||||||
|
description = Stockage interne
|
||||||
|
storage_id = 0x00010001
|
||||||
|
access = read/write
|
||||||
|
```
|
||||||
|
|
||||||
|
Trainlog created:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Stockage interne/
|
||||||
|
└── Trainlog/
|
||||||
|
└── trainlog-probe.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
The probe was then listed, downloaded back to the Linux host, and compared
|
||||||
|
byte-for-byte with the original local content.
|
||||||
|
|
||||||
|
Final result:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ROUNDTRIP=PASS Trainlog/trainlog-probe.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Boundary
|
||||||
|
|
||||||
|
This checkpoint validates the transport foundation only.
|
||||||
|
|
||||||
|
It does **not** yet claim that a real Trainlog JSON v1 session has been
|
||||||
|
transferred or imported.
|
||||||
|
|
||||||
|
Next:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. define the Trainlog MTP exchange directory layout
|
||||||
|
2. transfer a real valid JSON v1 document
|
||||||
|
3. verify byte-for-byte readback
|
||||||
|
4. feed the downloaded document to the frozen v1 validator/import path
|
||||||
|
5. build the minimal Android recorder on top of that contract
|
||||||
|
```
|
||||||
|
|
@ -198,3 +198,35 @@ ANDROID_MINIMAL_RECORDER=AFTER
|
||||||
JSON_V1_USB_IMPORT_EXPORT=AFTER
|
JSON_V1_USB_IMPORT_EXPORT=AFTER
|
||||||
```
|
```
|
||||||
<!-- TRAINLOG_EDITABILITY_ROADMAP _END -->
|
<!-- TRAINLOG_EDITABILITY_ROADMAP _END -->
|
||||||
|
|
||||||
|
<!-- TRAINLOG_MTP_TRANSPORT_ROADMAP -->
|
||||||
|
## Direct MTP transport checkpoint
|
||||||
|
|
||||||
|
```text
|
||||||
|
USB_MTP_DETECTION=PASS
|
||||||
|
MTP_STORAGE_ACCESS=PASS
|
||||||
|
MTP_ROOT_FOLDER_ACCESS=PASS
|
||||||
|
MTP_WRITE=PASS
|
||||||
|
MTP_LIST_FOLDER=PASS
|
||||||
|
MTP_READ=PASS
|
||||||
|
MTP_ROUNDTRIP=PASS
|
||||||
|
MTP_TRANSPORT_FOUNDATION=PASS
|
||||||
|
JSON_V1_MTP_TRANSFER=NEXT
|
||||||
|
ANDROID_MINIMAL_RECORDER=AFTER
|
||||||
|
TRAINLOG_FORMAT_V1=FROZEN
|
||||||
|
```
|
||||||
|
|
||||||
|
The Linux side now detects one physical MTP phone without counting USB
|
||||||
|
interface children, opens the exact device with libmtp, accesses internal
|
||||||
|
storage, and performs a verified write/list/read roundtrip without mounting the
|
||||||
|
phone.
|
||||||
|
|
||||||
|
Next implementation slice:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. freeze the MTP exchange directory/file convention
|
||||||
|
2. roundtrip a real examples/session-v1.json
|
||||||
|
3. validate/import the downloaded JSON
|
||||||
|
4. begin the minimal Android recorder
|
||||||
|
```
|
||||||
|
<!-- TRAINLOG_MTP_TRANSPORT_ROADMAP _END -->
|
||||||
|
|
|
||||||
|
|
@ -153,3 +153,40 @@ python tools/validate_json.py
|
||||||
python tools/validate_import_contract.py
|
python tools/validate_import_contract.py
|
||||||
git diff --check
|
git diff --check
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<!-- TRAINLOG_MTP_VALIDATION -->
|
||||||
|
## 11. USB/MTP transport validation
|
||||||
|
|
||||||
|
The compiled suite now contains:
|
||||||
|
|
||||||
|
```text
|
||||||
|
usb
|
||||||
|
mtp
|
||||||
|
```
|
||||||
|
|
||||||
|
The `usb` test covers API validation, physical-device enumeration invariants,
|
||||||
|
and rejection of duplicated USB interface children.
|
||||||
|
|
||||||
|
The `mtp` test covers bounded API argument validation for storage, folder,
|
||||||
|
upload, listing, and download entry points.
|
||||||
|
|
||||||
|
Hardware probes additionally validate the real device path:
|
||||||
|
|
||||||
|
```text
|
||||||
|
trainlog-usb-probe
|
||||||
|
trainlog-mtp-probe
|
||||||
|
trainlog-mtp-exchange-probe
|
||||||
|
trainlog-mtp-roundtrip-probe
|
||||||
|
```
|
||||||
|
|
||||||
|
Physical checkpoint result:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MTP devices: 1
|
||||||
|
ROUNDTRIP=PASS Trainlog/trainlog-probe.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
The hardware probe is intentionally separate from the normal automated test
|
||||||
|
suite because CI is not expected to have a connected unlocked Android MTP
|
||||||
|
device.
|
||||||
|
<!-- TRAINLOG_MTP_VALIDATION _END -->
|
||||||
|
|
|
||||||
107
tui/include/trainlog/mtp.h
Normal file
107
tui/include/trainlog/mtp.h
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
#ifndef TRAINLOG_MTP_H
|
||||||
|
#define TRAINLOG_MTP_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file mtp.h
|
||||||
|
* @brief MTP device access and storage enumeration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "trainlog/status.h"
|
||||||
|
|
||||||
|
#define TRAINLOG_MTP_STORAGE_TEXT_MAX 255U
|
||||||
|
|
||||||
|
typedef struct TrainlogMtpStorage {
|
||||||
|
uint32_t storage_id;
|
||||||
|
uint16_t storage_type;
|
||||||
|
uint16_t filesystem_type;
|
||||||
|
uint16_t access_capability;
|
||||||
|
uint64_t max_capacity_bytes;
|
||||||
|
uint64_t free_space_bytes;
|
||||||
|
uint64_t free_space_objects;
|
||||||
|
char description[TRAINLOG_MTP_STORAGE_TEXT_MAX + 1U];
|
||||||
|
char volume_identifier[TRAINLOG_MTP_STORAGE_TEXT_MAX + 1U];
|
||||||
|
} TrainlogMtpStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Open one exact MTP device and list its storage areas.
|
||||||
|
*
|
||||||
|
* bus_number/device_number come from TrainlogUsbDevice.
|
||||||
|
*
|
||||||
|
* Passing output=NULL with capacity=0 is a count-only query.
|
||||||
|
* Insufficient output capacity is rejected rather than silently truncated.
|
||||||
|
*/
|
||||||
|
TrainlogStatus trainlog_mtp_list_storages(
|
||||||
|
unsigned int bus_number,
|
||||||
|
unsigned int device_number,
|
||||||
|
TrainlogMtpStorage *output,
|
||||||
|
size_t capacity,
|
||||||
|
size_t *output_count
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find or create one root folder on an exact MTP storage.
|
||||||
|
*/
|
||||||
|
TrainlogStatus trainlog_mtp_ensure_root_folder(
|
||||||
|
unsigned int bus_number,
|
||||||
|
unsigned int device_number,
|
||||||
|
uint32_t storage_id,
|
||||||
|
const char *folder_name,
|
||||||
|
uint32_t *output_folder_id,
|
||||||
|
bool *output_created
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Upload one local text file into an exact MTP folder.
|
||||||
|
*/
|
||||||
|
TrainlogStatus trainlog_mtp_send_text_file(
|
||||||
|
unsigned int bus_number,
|
||||||
|
unsigned int device_number,
|
||||||
|
uint32_t storage_id,
|
||||||
|
uint32_t parent_folder_id,
|
||||||
|
const char *local_path,
|
||||||
|
const char *remote_filename,
|
||||||
|
uint32_t *output_item_id
|
||||||
|
);
|
||||||
|
|
||||||
|
#define TRAINLOG_MTP_ENTRY_NAME_MAX 255U
|
||||||
|
|
||||||
|
typedef struct TrainlogMtpEntry {
|
||||||
|
uint32_t item_id;
|
||||||
|
uint32_t parent_id;
|
||||||
|
uint32_t storage_id;
|
||||||
|
uint64_t size_bytes;
|
||||||
|
bool folder;
|
||||||
|
char name[TRAINLOG_MTP_ENTRY_NAME_MAX + 1U];
|
||||||
|
} TrainlogMtpEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief List direct children of one MTP folder.
|
||||||
|
*
|
||||||
|
* Passing output=NULL with capacity=0 is a count-only query.
|
||||||
|
* Insufficient capacity is rejected rather than silently truncated.
|
||||||
|
*/
|
||||||
|
TrainlogStatus trainlog_mtp_list_folder(
|
||||||
|
unsigned int bus_number,
|
||||||
|
unsigned int device_number,
|
||||||
|
uint32_t storage_id,
|
||||||
|
uint32_t parent_folder_id,
|
||||||
|
TrainlogMtpEntry *output,
|
||||||
|
size_t capacity,
|
||||||
|
size_t *output_count
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Download one MTP object to a local file.
|
||||||
|
*/
|
||||||
|
TrainlogStatus trainlog_mtp_receive_file(
|
||||||
|
unsigned int bus_number,
|
||||||
|
unsigned int device_number,
|
||||||
|
uint32_t item_id,
|
||||||
|
const char *local_path
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
32
tui/include/trainlog/usb.h
Normal file
32
tui/include/trainlog/usb.h
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef TRAINLOG_USB_H
|
||||||
|
#define TRAINLOG_USB_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "trainlog/status.h"
|
||||||
|
|
||||||
|
#define TRAINLOG_USB_VENDOR_MAX 127U
|
||||||
|
#define TRAINLOG_USB_MODEL_MAX 127U
|
||||||
|
#define TRAINLOG_USB_SERIAL_MAX 255U
|
||||||
|
#define TRAINLOG_USB_SYSPATH_MAX 511U
|
||||||
|
|
||||||
|
typedef struct TrainlogUsbDevice {
|
||||||
|
unsigned int bus_number;
|
||||||
|
unsigned int device_number;
|
||||||
|
unsigned int vendor_id;
|
||||||
|
unsigned int product_id;
|
||||||
|
bool mtp;
|
||||||
|
char vendor[TRAINLOG_USB_VENDOR_MAX + 1U];
|
||||||
|
char model[TRAINLOG_USB_MODEL_MAX + 1U];
|
||||||
|
char serial[TRAINLOG_USB_SERIAL_MAX + 1U];
|
||||||
|
char syspath[TRAINLOG_USB_SYSPATH_MAX + 1U];
|
||||||
|
} TrainlogUsbDevice;
|
||||||
|
|
||||||
|
TrainlogStatus trainlog_usb_list_mtp_devices(
|
||||||
|
TrainlogUsbDevice *output,
|
||||||
|
size_t capacity,
|
||||||
|
size_t *output_count
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -2,6 +2,8 @@ cc = meson.get_compiler('c')
|
||||||
|
|
||||||
sqlite3_dep = dependency('sqlite3', required: true)
|
sqlite3_dep = dependency('sqlite3', required: true)
|
||||||
uuid_dep = dependency('uuid', required: true)
|
uuid_dep = dependency('uuid', required: true)
|
||||||
|
udev_dep = dependency('libudev', required: true)
|
||||||
|
mtp_dep = dependency('libmtp', required: true)
|
||||||
|
|
||||||
ncursesw_dep = dependency('ncursesw', required: false)
|
ncursesw_dep = dependency('ncursesw', required: false)
|
||||||
if not ncursesw_dep.found()
|
if not ncursesw_dep.found()
|
||||||
|
|
@ -31,6 +33,8 @@ trainlog_core_sources = files(
|
||||||
'src/duration.c',
|
'src/duration.c',
|
||||||
'src/id.c',
|
'src/id.c',
|
||||||
'src/timeutil.c',
|
'src/timeutil.c',
|
||||||
|
'src/usb.c',
|
||||||
|
'src/mtp.c',
|
||||||
)
|
)
|
||||||
|
|
||||||
trainlog_core = static_library(
|
trainlog_core = static_library(
|
||||||
|
|
@ -42,6 +46,8 @@ trainlog_core = static_library(
|
||||||
uuid_dep,
|
uuid_dep,
|
||||||
utf8proc_dep,
|
utf8proc_dep,
|
||||||
m_dep,
|
m_dep,
|
||||||
|
udev_dep,
|
||||||
|
mtp_dep,
|
||||||
],
|
],
|
||||||
c_args: strict_c_args,
|
c_args: strict_c_args,
|
||||||
)
|
)
|
||||||
|
|
@ -54,6 +60,8 @@ trainlog_core_dep = declare_dependency(
|
||||||
uuid_dep,
|
uuid_dep,
|
||||||
utf8proc_dep,
|
utf8proc_dep,
|
||||||
m_dep,
|
m_dep,
|
||||||
|
udev_dep,
|
||||||
|
mtp_dep,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -183,3 +191,55 @@ test(
|
||||||
'body_observation_edit',
|
'body_observation_edit',
|
||||||
test_body_observation_edit,
|
test_body_observation_edit,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
trainlog_usb_probe = executable(
|
||||||
|
'trainlog-usb-probe',
|
||||||
|
'tools/usb_probe.c',
|
||||||
|
dependencies: trainlog_core_dep,
|
||||||
|
c_args: strict_c_args,
|
||||||
|
)
|
||||||
|
|
||||||
|
test_usb = executable(
|
||||||
|
'test_usb',
|
||||||
|
'tests/test_usb.c',
|
||||||
|
dependencies: trainlog_core_dep,
|
||||||
|
c_args: strict_c_args,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'usb',
|
||||||
|
test_usb,
|
||||||
|
)
|
||||||
|
|
||||||
|
trainlog_mtp_probe = executable(
|
||||||
|
'trainlog-mtp-probe',
|
||||||
|
'tools/mtp_probe.c',
|
||||||
|
dependencies: trainlog_core_dep,
|
||||||
|
c_args: strict_c_args,
|
||||||
|
)
|
||||||
|
|
||||||
|
test_mtp = executable(
|
||||||
|
'test_mtp',
|
||||||
|
'tests/test_mtp.c',
|
||||||
|
dependencies: trainlog_core_dep,
|
||||||
|
c_args: strict_c_args,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'mtp',
|
||||||
|
test_mtp,
|
||||||
|
)
|
||||||
|
|
||||||
|
trainlog_mtp_exchange_probe = executable(
|
||||||
|
'trainlog-mtp-exchange-probe',
|
||||||
|
'tools/mtp_exchange_probe.c',
|
||||||
|
dependencies: trainlog_core_dep,
|
||||||
|
c_args: strict_c_args,
|
||||||
|
)
|
||||||
|
|
||||||
|
trainlog_mtp_roundtrip_probe = executable(
|
||||||
|
'trainlog-mtp-roundtrip-probe',
|
||||||
|
'tools/mtp_roundtrip_probe.c',
|
||||||
|
dependencies: trainlog_core_dep,
|
||||||
|
c_args: strict_c_args,
|
||||||
|
)
|
||||||
|
|
|
||||||
779
tui/src/mtp.c
Normal file
779
tui/src/mtp.c
Normal file
|
|
@ -0,0 +1,779 @@
|
||||||
|
/**
|
||||||
|
* @file mtp.c
|
||||||
|
* @brief libmtp-backed storage access.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "trainlog/mtp.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <libmtp.h>
|
||||||
|
|
||||||
|
static void mtp_copy_text(
|
||||||
|
char *output,
|
||||||
|
size_t capacity,
|
||||||
|
const char *value
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (output == NULL ||
|
||||||
|
capacity == 0U) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == NULL) {
|
||||||
|
output[0] = '\0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)snprintf(
|
||||||
|
output,
|
||||||
|
capacity,
|
||||||
|
"%s",
|
||||||
|
value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mtp_raw_device_matches(
|
||||||
|
const LIBMTP_raw_device_t *device,
|
||||||
|
unsigned int bus_number,
|
||||||
|
unsigned int device_number
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (device == NULL ||
|
||||||
|
device_number > UINT8_MAX) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
device->bus_location ==
|
||||||
|
(uint32_t)bus_number &&
|
||||||
|
device->devnum ==
|
||||||
|
(uint8_t)device_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TrainlogStatus mtp_detect_raw_devices(
|
||||||
|
LIBMTP_raw_device_t **output_devices,
|
||||||
|
int *output_count
|
||||||
|
)
|
||||||
|
{
|
||||||
|
LIBMTP_error_number_t error;
|
||||||
|
|
||||||
|
if (output_devices == NULL ||
|
||||||
|
output_count == NULL) {
|
||||||
|
return
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_devices = NULL;
|
||||||
|
*output_count = 0;
|
||||||
|
|
||||||
|
LIBMTP_Init();
|
||||||
|
|
||||||
|
error =
|
||||||
|
LIBMTP_Detect_Raw_Devices(
|
||||||
|
output_devices,
|
||||||
|
output_count
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (error) {
|
||||||
|
case LIBMTP_ERROR_NONE:
|
||||||
|
return TRAINLOG_STATUS_OK;
|
||||||
|
|
||||||
|
case LIBMTP_ERROR_NO_DEVICE_ATTACHED:
|
||||||
|
return TRAINLOG_STATUS_NOT_FOUND;
|
||||||
|
|
||||||
|
case LIBMTP_ERROR_MEMORY_ALLOCATION:
|
||||||
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
|
|
||||||
|
case LIBMTP_ERROR_GENERAL:
|
||||||
|
case LIBMTP_ERROR_PTP_LAYER:
|
||||||
|
case LIBMTP_ERROR_USB_LAYER:
|
||||||
|
case LIBMTP_ERROR_STORAGE_FULL:
|
||||||
|
case LIBMTP_ERROR_CONNECTING:
|
||||||
|
case LIBMTP_ERROR_CANCELLED:
|
||||||
|
default:
|
||||||
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TrainlogStatus trainlog_mtp_list_storages(
|
||||||
|
unsigned int bus_number,
|
||||||
|
unsigned int device_number,
|
||||||
|
TrainlogMtpStorage *output,
|
||||||
|
size_t capacity,
|
||||||
|
size_t *output_count
|
||||||
|
)
|
||||||
|
{
|
||||||
|
LIBMTP_raw_device_t *raw_devices = NULL;
|
||||||
|
LIBMTP_mtpdevice_t *device = NULL;
|
||||||
|
LIBMTP_devicestorage_t *storage;
|
||||||
|
TrainlogStatus status;
|
||||||
|
int raw_count = 0;
|
||||||
|
int index;
|
||||||
|
size_t discovered = 0U;
|
||||||
|
|
||||||
|
if (output_count == NULL ||
|
||||||
|
(output == NULL &&
|
||||||
|
capacity != 0U) ||
|
||||||
|
device_number > UINT8_MAX) {
|
||||||
|
return
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_count = 0U;
|
||||||
|
|
||||||
|
status =
|
||||||
|
mtp_detect_raw_devices(
|
||||||
|
&raw_devices,
|
||||||
|
&raw_count
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
free(raw_devices);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (index = 0;
|
||||||
|
index < raw_count;
|
||||||
|
++index) {
|
||||||
|
if (mtp_raw_device_matches(
|
||||||
|
&raw_devices[index],
|
||||||
|
bus_number,
|
||||||
|
device_number
|
||||||
|
)) {
|
||||||
|
device =
|
||||||
|
LIBMTP_Open_Raw_Device(
|
||||||
|
&raw_devices[index]
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device == NULL) {
|
||||||
|
free(raw_devices);
|
||||||
|
return TRAINLOG_STATUS_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LIBMTP_Get_Storage(
|
||||||
|
device,
|
||||||
|
LIBMTP_STORAGE_SORTBY_NOTSORTED
|
||||||
|
) != 0) {
|
||||||
|
LIBMTP_Clear_Errorstack(device);
|
||||||
|
LIBMTP_Release_Device(device);
|
||||||
|
free(raw_devices);
|
||||||
|
|
||||||
|
return
|
||||||
|
TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
storage = device->storage;
|
||||||
|
|
||||||
|
while (storage != NULL) {
|
||||||
|
if (output != NULL &&
|
||||||
|
discovered < capacity) {
|
||||||
|
TrainlogMtpStorage *item =
|
||||||
|
&output[discovered];
|
||||||
|
|
||||||
|
(void)memset(
|
||||||
|
item,
|
||||||
|
0,
|
||||||
|
sizeof(*item)
|
||||||
|
);
|
||||||
|
|
||||||
|
item->storage_id =
|
||||||
|
storage->id;
|
||||||
|
|
||||||
|
item->storage_type =
|
||||||
|
storage->StorageType;
|
||||||
|
|
||||||
|
item->filesystem_type =
|
||||||
|
storage->FilesystemType;
|
||||||
|
|
||||||
|
item->access_capability =
|
||||||
|
storage->AccessCapability;
|
||||||
|
|
||||||
|
item->max_capacity_bytes =
|
||||||
|
storage->MaxCapacity;
|
||||||
|
|
||||||
|
item->free_space_bytes =
|
||||||
|
storage->FreeSpaceInBytes;
|
||||||
|
|
||||||
|
item->free_space_objects =
|
||||||
|
storage->FreeSpaceInObjects;
|
||||||
|
|
||||||
|
mtp_copy_text(
|
||||||
|
item->description,
|
||||||
|
sizeof(item->description),
|
||||||
|
storage->StorageDescription
|
||||||
|
);
|
||||||
|
|
||||||
|
mtp_copy_text(
|
||||||
|
item->volume_identifier,
|
||||||
|
sizeof(item->volume_identifier),
|
||||||
|
storage->VolumeIdentifier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
++discovered;
|
||||||
|
storage = storage->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_count =
|
||||||
|
discovered;
|
||||||
|
|
||||||
|
if (output != NULL &&
|
||||||
|
discovered > capacity) {
|
||||||
|
status =
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
} else {
|
||||||
|
status =
|
||||||
|
TRAINLOG_STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
LIBMTP_Release_Device(device);
|
||||||
|
free(raw_devices);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TrainlogStatus mtp_open_exact_device(
|
||||||
|
unsigned int bus_number,
|
||||||
|
unsigned int device_number,
|
||||||
|
LIBMTP_mtpdevice_t **output_device
|
||||||
|
)
|
||||||
|
{
|
||||||
|
LIBMTP_raw_device_t *raw_devices = NULL;
|
||||||
|
LIBMTP_mtpdevice_t *device = NULL;
|
||||||
|
TrainlogStatus status;
|
||||||
|
int raw_count = 0;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
if (output_device == NULL ||
|
||||||
|
device_number > UINT8_MAX) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_device = NULL;
|
||||||
|
|
||||||
|
status =
|
||||||
|
mtp_detect_raw_devices(
|
||||||
|
&raw_devices,
|
||||||
|
&raw_count
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
free(raw_devices);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (index = 0;
|
||||||
|
index < raw_count;
|
||||||
|
++index) {
|
||||||
|
if (mtp_raw_device_matches(
|
||||||
|
&raw_devices[index],
|
||||||
|
bus_number,
|
||||||
|
device_number
|
||||||
|
)) {
|
||||||
|
device =
|
||||||
|
LIBMTP_Open_Raw_Device(
|
||||||
|
&raw_devices[index]
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(raw_devices);
|
||||||
|
|
||||||
|
if (device == NULL) {
|
||||||
|
return TRAINLOG_STATUS_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_device = device;
|
||||||
|
|
||||||
|
return TRAINLOG_STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TrainlogStatus mtp_open_exact_device_uncached(
|
||||||
|
unsigned int bus_number,
|
||||||
|
unsigned int device_number,
|
||||||
|
LIBMTP_mtpdevice_t **output_device
|
||||||
|
)
|
||||||
|
{
|
||||||
|
LIBMTP_raw_device_t *raw_devices = NULL;
|
||||||
|
LIBMTP_mtpdevice_t *device = NULL;
|
||||||
|
TrainlogStatus status;
|
||||||
|
int raw_count = 0;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
if (output_device == NULL ||
|
||||||
|
device_number > UINT8_MAX) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_device = NULL;
|
||||||
|
|
||||||
|
status =
|
||||||
|
mtp_detect_raw_devices(
|
||||||
|
&raw_devices,
|
||||||
|
&raw_count
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
free(raw_devices);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (index = 0;
|
||||||
|
index < raw_count;
|
||||||
|
++index) {
|
||||||
|
if (mtp_raw_device_matches(
|
||||||
|
&raw_devices[index],
|
||||||
|
bus_number,
|
||||||
|
device_number
|
||||||
|
)) {
|
||||||
|
device =
|
||||||
|
LIBMTP_Open_Raw_Device_Uncached(
|
||||||
|
&raw_devices[index]
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(raw_devices);
|
||||||
|
|
||||||
|
if (device == NULL) {
|
||||||
|
return TRAINLOG_STATUS_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_device = device;
|
||||||
|
|
||||||
|
return TRAINLOG_STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LIBMTP_folder_t *mtp_find_root_folder(
|
||||||
|
LIBMTP_folder_t *folders,
|
||||||
|
uint32_t storage_id,
|
||||||
|
const char *folder_name
|
||||||
|
)
|
||||||
|
{
|
||||||
|
LIBMTP_folder_t *folder;
|
||||||
|
|
||||||
|
if (folder_name == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (folder = folders;
|
||||||
|
folder != NULL;
|
||||||
|
folder = folder->sibling) {
|
||||||
|
if (folder->storage_id == storage_id &&
|
||||||
|
folder->name != NULL &&
|
||||||
|
strcmp(
|
||||||
|
folder->name,
|
||||||
|
folder_name
|
||||||
|
) == 0) {
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *mtp_duplicate_text(
|
||||||
|
const char *text
|
||||||
|
)
|
||||||
|
{
|
||||||
|
size_t length;
|
||||||
|
char *copy;
|
||||||
|
|
||||||
|
if (text == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
length = strlen(text);
|
||||||
|
|
||||||
|
copy =
|
||||||
|
malloc(
|
||||||
|
length + 1U
|
||||||
|
);
|
||||||
|
|
||||||
|
if (copy == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)memcpy(
|
||||||
|
copy,
|
||||||
|
text,
|
||||||
|
length + 1U
|
||||||
|
);
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrainlogStatus trainlog_mtp_ensure_root_folder(
|
||||||
|
unsigned int bus_number,
|
||||||
|
unsigned int device_number,
|
||||||
|
uint32_t storage_id,
|
||||||
|
const char *folder_name,
|
||||||
|
uint32_t *output_folder_id,
|
||||||
|
bool *output_created
|
||||||
|
)
|
||||||
|
{
|
||||||
|
LIBMTP_mtpdevice_t *device = NULL;
|
||||||
|
LIBMTP_folder_t *folders = NULL;
|
||||||
|
LIBMTP_folder_t *existing;
|
||||||
|
TrainlogStatus status;
|
||||||
|
uint32_t folder_id;
|
||||||
|
char mutable_name[256];
|
||||||
|
|
||||||
|
if (folder_name == NULL ||
|
||||||
|
folder_name[0] == '\0' ||
|
||||||
|
output_folder_id == NULL ||
|
||||||
|
output_created == NULL ||
|
||||||
|
strlen(folder_name) >= sizeof(mutable_name)) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_folder_id = 0U;
|
||||||
|
*output_created = false;
|
||||||
|
|
||||||
|
status =
|
||||||
|
mtp_open_exact_device(
|
||||||
|
bus_number,
|
||||||
|
device_number,
|
||||||
|
&device
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
folders =
|
||||||
|
LIBMTP_Get_Folder_List_For_Storage(
|
||||||
|
device,
|
||||||
|
storage_id
|
||||||
|
);
|
||||||
|
|
||||||
|
existing =
|
||||||
|
mtp_find_root_folder(
|
||||||
|
folders,
|
||||||
|
storage_id,
|
||||||
|
folder_name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existing != NULL) {
|
||||||
|
*output_folder_id =
|
||||||
|
existing->folder_id;
|
||||||
|
|
||||||
|
if (folders != NULL) {
|
||||||
|
LIBMTP_destroy_folder_t(folders);
|
||||||
|
}
|
||||||
|
|
||||||
|
LIBMTP_Release_Device(device);
|
||||||
|
|
||||||
|
return TRAINLOG_STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)snprintf(
|
||||||
|
mutable_name,
|
||||||
|
sizeof(mutable_name),
|
||||||
|
"%s",
|
||||||
|
folder_name
|
||||||
|
);
|
||||||
|
|
||||||
|
folder_id =
|
||||||
|
LIBMTP_Create_Folder(
|
||||||
|
device,
|
||||||
|
mutable_name,
|
||||||
|
UINT32_MAX,
|
||||||
|
storage_id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (folders != NULL) {
|
||||||
|
LIBMTP_destroy_folder_t(folders);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (folder_id == 0U) {
|
||||||
|
LIBMTP_Clear_Errorstack(device);
|
||||||
|
LIBMTP_Release_Device(device);
|
||||||
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_folder_id = folder_id;
|
||||||
|
*output_created = true;
|
||||||
|
|
||||||
|
LIBMTP_Release_Device(device);
|
||||||
|
|
||||||
|
return TRAINLOG_STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrainlogStatus trainlog_mtp_send_text_file(
|
||||||
|
unsigned int bus_number,
|
||||||
|
unsigned int device_number,
|
||||||
|
uint32_t storage_id,
|
||||||
|
uint32_t parent_folder_id,
|
||||||
|
const char *local_path,
|
||||||
|
const char *remote_filename,
|
||||||
|
uint32_t *output_item_id
|
||||||
|
)
|
||||||
|
{
|
||||||
|
struct stat file_stat;
|
||||||
|
LIBMTP_mtpdevice_t *device = NULL;
|
||||||
|
LIBMTP_file_t *metadata = NULL;
|
||||||
|
TrainlogStatus status;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (local_path == NULL ||
|
||||||
|
local_path[0] == '\0' ||
|
||||||
|
remote_filename == NULL ||
|
||||||
|
remote_filename[0] == '\0' ||
|
||||||
|
output_item_id == NULL) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_item_id = 0U;
|
||||||
|
|
||||||
|
if (stat(
|
||||||
|
local_path,
|
||||||
|
&file_stat
|
||||||
|
) != 0 ||
|
||||||
|
file_stat.st_size < 0) {
|
||||||
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
status =
|
||||||
|
mtp_open_exact_device(
|
||||||
|
bus_number,
|
||||||
|
device_number,
|
||||||
|
&device
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata =
|
||||||
|
LIBMTP_new_file_t();
|
||||||
|
|
||||||
|
if (metadata == NULL) {
|
||||||
|
LIBMTP_Release_Device(device);
|
||||||
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata->filename =
|
||||||
|
mtp_duplicate_text(
|
||||||
|
remote_filename
|
||||||
|
);
|
||||||
|
|
||||||
|
if (metadata->filename == NULL) {
|
||||||
|
LIBMTP_destroy_file_t(metadata);
|
||||||
|
LIBMTP_Release_Device(device);
|
||||||
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata->parent_id =
|
||||||
|
parent_folder_id;
|
||||||
|
|
||||||
|
metadata->storage_id =
|
||||||
|
storage_id;
|
||||||
|
|
||||||
|
metadata->filesize =
|
||||||
|
(uint64_t)file_stat.st_size;
|
||||||
|
|
||||||
|
metadata->filetype =
|
||||||
|
LIBMTP_FILETYPE_TEXT;
|
||||||
|
|
||||||
|
rc =
|
||||||
|
LIBMTP_Send_File_From_File(
|
||||||
|
device,
|
||||||
|
local_path,
|
||||||
|
metadata,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rc != 0) {
|
||||||
|
LIBMTP_Clear_Errorstack(device);
|
||||||
|
LIBMTP_destroy_file_t(metadata);
|
||||||
|
LIBMTP_Release_Device(device);
|
||||||
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_item_id =
|
||||||
|
metadata->item_id;
|
||||||
|
|
||||||
|
LIBMTP_destroy_file_t(metadata);
|
||||||
|
LIBMTP_Release_Device(device);
|
||||||
|
|
||||||
|
return TRAINLOG_STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrainlogStatus trainlog_mtp_list_folder(
|
||||||
|
unsigned int bus_number,
|
||||||
|
unsigned int device_number,
|
||||||
|
uint32_t storage_id,
|
||||||
|
uint32_t parent_folder_id,
|
||||||
|
TrainlogMtpEntry *output,
|
||||||
|
size_t capacity,
|
||||||
|
size_t *output_count
|
||||||
|
)
|
||||||
|
{
|
||||||
|
LIBMTP_mtpdevice_t *device = NULL;
|
||||||
|
LIBMTP_file_t *items = NULL;
|
||||||
|
LIBMTP_file_t *item;
|
||||||
|
TrainlogStatus status;
|
||||||
|
size_t discovered = 0U;
|
||||||
|
|
||||||
|
if (output_count == NULL ||
|
||||||
|
(output == NULL &&
|
||||||
|
capacity != 0U)) {
|
||||||
|
return
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_count = 0U;
|
||||||
|
|
||||||
|
status =
|
||||||
|
mtp_open_exact_device_uncached(
|
||||||
|
bus_number,
|
||||||
|
device_number,
|
||||||
|
&device
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
items =
|
||||||
|
LIBMTP_Get_Files_And_Folders(
|
||||||
|
device,
|
||||||
|
storage_id,
|
||||||
|
parent_folder_id
|
||||||
|
);
|
||||||
|
|
||||||
|
for (item = items;
|
||||||
|
item != NULL;
|
||||||
|
item = item->next) {
|
||||||
|
if (output != NULL &&
|
||||||
|
discovered < capacity) {
|
||||||
|
TrainlogMtpEntry *entry =
|
||||||
|
&output[discovered];
|
||||||
|
|
||||||
|
(void)memset(
|
||||||
|
entry,
|
||||||
|
0,
|
||||||
|
sizeof(*entry)
|
||||||
|
);
|
||||||
|
|
||||||
|
entry->item_id =
|
||||||
|
item->item_id;
|
||||||
|
|
||||||
|
entry->parent_id =
|
||||||
|
item->parent_id;
|
||||||
|
|
||||||
|
entry->storage_id =
|
||||||
|
item->storage_id;
|
||||||
|
|
||||||
|
entry->size_bytes =
|
||||||
|
item->filesize;
|
||||||
|
|
||||||
|
entry->folder =
|
||||||
|
item->filetype ==
|
||||||
|
LIBMTP_FILETYPE_FOLDER;
|
||||||
|
|
||||||
|
mtp_copy_text(
|
||||||
|
entry->name,
|
||||||
|
sizeof(entry->name),
|
||||||
|
item->filename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
++discovered;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items != NULL) {
|
||||||
|
LIBMTP_destroy_file_t(
|
||||||
|
items
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LIBMTP_Release_Device(
|
||||||
|
device
|
||||||
|
);
|
||||||
|
|
||||||
|
*output_count =
|
||||||
|
discovered;
|
||||||
|
|
||||||
|
if (output != NULL &&
|
||||||
|
discovered > capacity) {
|
||||||
|
return
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
TRAINLOG_STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrainlogStatus trainlog_mtp_receive_file(
|
||||||
|
unsigned int bus_number,
|
||||||
|
unsigned int device_number,
|
||||||
|
uint32_t item_id,
|
||||||
|
const char *local_path
|
||||||
|
)
|
||||||
|
{
|
||||||
|
LIBMTP_mtpdevice_t *device = NULL;
|
||||||
|
TrainlogStatus status;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (item_id == 0U ||
|
||||||
|
local_path == NULL ||
|
||||||
|
local_path[0] == '\0') {
|
||||||
|
return
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
status =
|
||||||
|
mtp_open_exact_device_uncached(
|
||||||
|
bus_number,
|
||||||
|
device_number,
|
||||||
|
&device
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc =
|
||||||
|
LIBMTP_Get_File_To_File(
|
||||||
|
device,
|
||||||
|
item_id,
|
||||||
|
local_path,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rc != 0) {
|
||||||
|
LIBMTP_Clear_Errorstack(
|
||||||
|
device
|
||||||
|
);
|
||||||
|
|
||||||
|
LIBMTP_Release_Device(
|
||||||
|
device
|
||||||
|
);
|
||||||
|
|
||||||
|
return
|
||||||
|
TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
LIBMTP_Release_Device(
|
||||||
|
device
|
||||||
|
);
|
||||||
|
|
||||||
|
return
|
||||||
|
TRAINLOG_STATUS_OK;
|
||||||
|
}
|
||||||
386
tui/src/usb.c
Normal file
386
tui/src/usb.c
Normal file
|
|
@ -0,0 +1,386 @@
|
||||||
|
#include "trainlog/usb.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <libudev.h>
|
||||||
|
|
||||||
|
static const char *usb_property_prefer(
|
||||||
|
struct udev_device *device,
|
||||||
|
const char *preferred,
|
||||||
|
const char *fallback
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const char *value =
|
||||||
|
udev_device_get_property_value(
|
||||||
|
device,
|
||||||
|
preferred
|
||||||
|
);
|
||||||
|
|
||||||
|
if (value != NULL &&
|
||||||
|
value[0] != '\0') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
udev_device_get_property_value(
|
||||||
|
device,
|
||||||
|
fallback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb_copy_text(
|
||||||
|
char *output,
|
||||||
|
size_t capacity,
|
||||||
|
const char *value
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (output == NULL ||
|
||||||
|
capacity == 0U) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == NULL) {
|
||||||
|
output[0] = '\0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)snprintf(
|
||||||
|
output,
|
||||||
|
capacity,
|
||||||
|
"%s",
|
||||||
|
value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usb_parse_unsigned(
|
||||||
|
const char *text,
|
||||||
|
int base,
|
||||||
|
unsigned int *output
|
||||||
|
)
|
||||||
|
{
|
||||||
|
char *end = NULL;
|
||||||
|
unsigned long value;
|
||||||
|
|
||||||
|
if (text == NULL ||
|
||||||
|
output == NULL ||
|
||||||
|
text[0] == '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
|
||||||
|
value =
|
||||||
|
strtoul(
|
||||||
|
text,
|
||||||
|
&end,
|
||||||
|
base
|
||||||
|
);
|
||||||
|
|
||||||
|
if (errno != 0 ||
|
||||||
|
end == text ||
|
||||||
|
*end != '\0' ||
|
||||||
|
value > (unsigned long)UINT_MAX) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output =
|
||||||
|
(unsigned int)value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usb_device_is_physical_mtp(
|
||||||
|
struct udev_device *device
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const char *devtype;
|
||||||
|
const char *mtp;
|
||||||
|
|
||||||
|
if (device == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
devtype =
|
||||||
|
udev_device_get_devtype(
|
||||||
|
device
|
||||||
|
);
|
||||||
|
|
||||||
|
if (devtype == NULL ||
|
||||||
|
strcmp(
|
||||||
|
devtype,
|
||||||
|
"usb_device"
|
||||||
|
) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mtp =
|
||||||
|
udev_device_get_property_value(
|
||||||
|
device,
|
||||||
|
"ID_MTP_DEVICE"
|
||||||
|
);
|
||||||
|
|
||||||
|
return
|
||||||
|
mtp != NULL &&
|
||||||
|
strcmp(mtp, "1") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb_fill_device(
|
||||||
|
struct udev_device *device,
|
||||||
|
TrainlogUsbDevice *output
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const char *vendor;
|
||||||
|
const char *model;
|
||||||
|
const char *serial;
|
||||||
|
const char *vendor_id;
|
||||||
|
const char *product_id;
|
||||||
|
const char *bus;
|
||||||
|
const char *devnum;
|
||||||
|
const char *syspath;
|
||||||
|
|
||||||
|
(void)memset(
|
||||||
|
output,
|
||||||
|
0,
|
||||||
|
sizeof(*output)
|
||||||
|
);
|
||||||
|
|
||||||
|
output->mtp = true;
|
||||||
|
|
||||||
|
vendor =
|
||||||
|
usb_property_prefer(
|
||||||
|
device,
|
||||||
|
"ID_VENDOR_FROM_DATABASE",
|
||||||
|
"ID_VENDOR"
|
||||||
|
);
|
||||||
|
|
||||||
|
model =
|
||||||
|
usb_property_prefer(
|
||||||
|
device,
|
||||||
|
"ID_MODEL_FROM_DATABASE",
|
||||||
|
"ID_MODEL"
|
||||||
|
);
|
||||||
|
|
||||||
|
serial =
|
||||||
|
udev_device_get_property_value(
|
||||||
|
device,
|
||||||
|
"ID_SERIAL_SHORT"
|
||||||
|
);
|
||||||
|
|
||||||
|
vendor_id =
|
||||||
|
udev_device_get_property_value(
|
||||||
|
device,
|
||||||
|
"ID_VENDOR_ID"
|
||||||
|
);
|
||||||
|
|
||||||
|
product_id =
|
||||||
|
udev_device_get_property_value(
|
||||||
|
device,
|
||||||
|
"ID_MODEL_ID"
|
||||||
|
);
|
||||||
|
|
||||||
|
bus =
|
||||||
|
udev_device_get_property_value(
|
||||||
|
device,
|
||||||
|
"BUSNUM"
|
||||||
|
);
|
||||||
|
|
||||||
|
devnum =
|
||||||
|
udev_device_get_property_value(
|
||||||
|
device,
|
||||||
|
"DEVNUM"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (bus == NULL) {
|
||||||
|
bus =
|
||||||
|
udev_device_get_sysattr_value(
|
||||||
|
device,
|
||||||
|
"busnum"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devnum == NULL) {
|
||||||
|
devnum =
|
||||||
|
udev_device_get_sysattr_value(
|
||||||
|
device,
|
||||||
|
"devnum"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
syspath =
|
||||||
|
udev_device_get_syspath(
|
||||||
|
device
|
||||||
|
);
|
||||||
|
|
||||||
|
usb_copy_text(
|
||||||
|
output->vendor,
|
||||||
|
sizeof(output->vendor),
|
||||||
|
vendor
|
||||||
|
);
|
||||||
|
|
||||||
|
usb_copy_text(
|
||||||
|
output->model,
|
||||||
|
sizeof(output->model),
|
||||||
|
model
|
||||||
|
);
|
||||||
|
|
||||||
|
usb_copy_text(
|
||||||
|
output->serial,
|
||||||
|
sizeof(output->serial),
|
||||||
|
serial
|
||||||
|
);
|
||||||
|
|
||||||
|
usb_copy_text(
|
||||||
|
output->syspath,
|
||||||
|
sizeof(output->syspath),
|
||||||
|
syspath
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)usb_parse_unsigned(
|
||||||
|
vendor_id,
|
||||||
|
16,
|
||||||
|
&output->vendor_id
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)usb_parse_unsigned(
|
||||||
|
product_id,
|
||||||
|
16,
|
||||||
|
&output->product_id
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)usb_parse_unsigned(
|
||||||
|
bus,
|
||||||
|
10,
|
||||||
|
&output->bus_number
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)usb_parse_unsigned(
|
||||||
|
devnum,
|
||||||
|
10,
|
||||||
|
&output->device_number
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TrainlogStatus trainlog_usb_list_mtp_devices(
|
||||||
|
TrainlogUsbDevice *output,
|
||||||
|
size_t capacity,
|
||||||
|
size_t *output_count
|
||||||
|
)
|
||||||
|
{
|
||||||
|
struct udev *udev = NULL;
|
||||||
|
struct udev_enumerate *enumerate = NULL;
|
||||||
|
struct udev_list_entry *devices;
|
||||||
|
struct udev_list_entry *entry;
|
||||||
|
size_t discovered = 0U;
|
||||||
|
TrainlogStatus status =
|
||||||
|
TRAINLOG_STATUS_OK;
|
||||||
|
|
||||||
|
if (output_count == NULL ||
|
||||||
|
(output == NULL &&
|
||||||
|
capacity != 0U)) {
|
||||||
|
return
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_count = 0U;
|
||||||
|
|
||||||
|
udev = udev_new();
|
||||||
|
|
||||||
|
if (udev == NULL) {
|
||||||
|
return
|
||||||
|
TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
enumerate =
|
||||||
|
udev_enumerate_new(udev);
|
||||||
|
|
||||||
|
if (enumerate == NULL) {
|
||||||
|
udev_unref(udev);
|
||||||
|
return
|
||||||
|
TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (udev_enumerate_add_match_subsystem(
|
||||||
|
enumerate,
|
||||||
|
"usb"
|
||||||
|
) < 0 ||
|
||||||
|
udev_enumerate_scan_devices(
|
||||||
|
enumerate
|
||||||
|
) < 0) {
|
||||||
|
status =
|
||||||
|
TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
devices =
|
||||||
|
udev_enumerate_get_list_entry(
|
||||||
|
enumerate
|
||||||
|
);
|
||||||
|
|
||||||
|
udev_list_entry_foreach(
|
||||||
|
entry,
|
||||||
|
devices
|
||||||
|
) {
|
||||||
|
const char *syspath =
|
||||||
|
udev_list_entry_get_name(
|
||||||
|
entry
|
||||||
|
);
|
||||||
|
|
||||||
|
struct udev_device *device;
|
||||||
|
|
||||||
|
if (syspath == NULL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
device =
|
||||||
|
udev_device_new_from_syspath(
|
||||||
|
udev,
|
||||||
|
syspath
|
||||||
|
);
|
||||||
|
|
||||||
|
if (device == NULL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usb_device_is_physical_mtp(
|
||||||
|
device
|
||||||
|
)) {
|
||||||
|
if (output != NULL &&
|
||||||
|
discovered < capacity) {
|
||||||
|
usb_fill_device(
|
||||||
|
device,
|
||||||
|
&output[discovered]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
++discovered;
|
||||||
|
}
|
||||||
|
|
||||||
|
udev_device_unref(
|
||||||
|
device
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
*output_count =
|
||||||
|
discovered;
|
||||||
|
|
||||||
|
if (output != NULL &&
|
||||||
|
discovered > capacity) {
|
||||||
|
status =
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
udev_enumerate_unref(
|
||||||
|
enumerate
|
||||||
|
);
|
||||||
|
|
||||||
|
udev_unref(
|
||||||
|
udev
|
||||||
|
);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
190
tui/tests/test_mtp.c
Normal file
190
tui/tests/test_mtp.c
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "trainlog/mtp.h"
|
||||||
|
#include "trainlog/status.h"
|
||||||
|
|
||||||
|
#define CHECK(condition) \
|
||||||
|
do { \
|
||||||
|
if (!(condition)) { \
|
||||||
|
(void)fprintf( \
|
||||||
|
stderr, \
|
||||||
|
"CHECK failed: %s:%d: %s\n", \
|
||||||
|
__FILE__, \
|
||||||
|
__LINE__, \
|
||||||
|
#condition \
|
||||||
|
); \
|
||||||
|
return 1; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static int test_argument_validation(void)
|
||||||
|
{
|
||||||
|
size_t count = 0U;
|
||||||
|
TrainlogMtpStorage storage;
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_mtp_list_storages(
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
NULL,
|
||||||
|
1U,
|
||||||
|
&count
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_mtp_list_storages(
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
&storage,
|
||||||
|
1U,
|
||||||
|
NULL
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_mtp_list_storages(
|
||||||
|
1U,
|
||||||
|
UINT_MAX,
|
||||||
|
&storage,
|
||||||
|
1U,
|
||||||
|
&count
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_exchange_argument_validation(void)
|
||||||
|
{
|
||||||
|
uint32_t folder_id = 0U;
|
||||||
|
uint32_t item_id = 0U;
|
||||||
|
bool created = false;
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_mtp_ensure_root_folder(
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
NULL,
|
||||||
|
&folder_id,
|
||||||
|
&created
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_mtp_ensure_root_folder(
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
"",
|
||||||
|
&folder_id,
|
||||||
|
&created
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_mtp_send_text_file(
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
NULL,
|
||||||
|
"probe.txt",
|
||||||
|
&item_id
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_mtp_send_text_file(
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
"/definitely/not/present/trainlog-probe",
|
||||||
|
"probe.txt",
|
||||||
|
&item_id
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_SYSTEM_ERROR
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_roundtrip_argument_validation(void)
|
||||||
|
{
|
||||||
|
size_t count = 0U;
|
||||||
|
TrainlogMtpEntry entry;
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_mtp_list_folder(
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
NULL,
|
||||||
|
1U,
|
||||||
|
&count
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_mtp_list_folder(
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
&entry,
|
||||||
|
1U,
|
||||||
|
NULL
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_mtp_receive_file(
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
0U,
|
||||||
|
"/tmp/trainlog-invalid"
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_mtp_receive_file(
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
1U,
|
||||||
|
NULL
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
CHECK(
|
||||||
|
test_argument_validation() == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
test_exchange_argument_validation() == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
test_roundtrip_argument_validation() == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
129
tui/tests/test_usb.c
Normal file
129
tui/tests/test_usb.c
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "trainlog/status.h"
|
||||||
|
#include "trainlog/usb.h"
|
||||||
|
|
||||||
|
#define CHECK(condition) \
|
||||||
|
do { \
|
||||||
|
if (!(condition)) { \
|
||||||
|
(void)fprintf( \
|
||||||
|
stderr, \
|
||||||
|
"CHECK failed: %s:%d: %s\n", \
|
||||||
|
__FILE__, \
|
||||||
|
__LINE__, \
|
||||||
|
#condition \
|
||||||
|
); \
|
||||||
|
return 1; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define TEST_CAPACITY 32U
|
||||||
|
|
||||||
|
static int test_argument_validation(void)
|
||||||
|
{
|
||||||
|
size_t count = 0U;
|
||||||
|
TrainlogUsbDevice device;
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_usb_list_mtp_devices(
|
||||||
|
NULL,
|
||||||
|
1U,
|
||||||
|
&count
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_usb_list_mtp_devices(
|
||||||
|
&device,
|
||||||
|
1U,
|
||||||
|
NULL
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_live_enumeration(void)
|
||||||
|
{
|
||||||
|
TrainlogUsbDevice
|
||||||
|
devices[TEST_CAPACITY];
|
||||||
|
|
||||||
|
size_t count_only = 0U;
|
||||||
|
size_t count = 0U;
|
||||||
|
size_t index;
|
||||||
|
size_t other;
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_usb_list_mtp_devices(
|
||||||
|
NULL,
|
||||||
|
0U,
|
||||||
|
&count_only
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_OK
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
count_only <=
|
||||||
|
TEST_CAPACITY
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
trainlog_usb_list_mtp_devices(
|
||||||
|
devices,
|
||||||
|
TEST_CAPACITY,
|
||||||
|
&count
|
||||||
|
) ==
|
||||||
|
TRAINLOG_STATUS_OK
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(count == count_only);
|
||||||
|
|
||||||
|
for (index = 0U;
|
||||||
|
index < count;
|
||||||
|
++index) {
|
||||||
|
CHECK(
|
||||||
|
devices[index].mtp
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
devices[index].syspath[0] !=
|
||||||
|
'\0'
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
strstr(
|
||||||
|
devices[index].syspath,
|
||||||
|
":1."
|
||||||
|
) == NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
for (other = index + 1U;
|
||||||
|
other < count;
|
||||||
|
++other) {
|
||||||
|
CHECK(
|
||||||
|
devices[index].bus_number !=
|
||||||
|
devices[other].bus_number ||
|
||||||
|
devices[index].device_number !=
|
||||||
|
devices[other].device_number
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
CHECK(
|
||||||
|
test_argument_validation() == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK(
|
||||||
|
test_live_enumeration() == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
186
tui/tools/mtp_exchange_probe.c
Normal file
186
tui/tools/mtp_exchange_probe.c
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "trainlog/mtp.h"
|
||||||
|
#include "trainlog/status.h"
|
||||||
|
#include "trainlog/usb.h"
|
||||||
|
|
||||||
|
#define DEVICE_CAPACITY 16U
|
||||||
|
#define STORAGE_CAPACITY 16U
|
||||||
|
|
||||||
|
static int create_probe_file(
|
||||||
|
char path[64]
|
||||||
|
)
|
||||||
|
{
|
||||||
|
static const char content[] =
|
||||||
|
"Trainlog MTP write probe\n"
|
||||||
|
"If you can read this file on Android, USB/MTP write works.\n";
|
||||||
|
|
||||||
|
int fd;
|
||||||
|
ssize_t written;
|
||||||
|
|
||||||
|
(void)snprintf(
|
||||||
|
path,
|
||||||
|
64U,
|
||||||
|
"%s",
|
||||||
|
"/tmp/trainlog-mtp-probe-XXXXXX"
|
||||||
|
);
|
||||||
|
|
||||||
|
fd = mkstemp(path);
|
||||||
|
|
||||||
|
if (fd < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
written =
|
||||||
|
write(
|
||||||
|
fd,
|
||||||
|
content,
|
||||||
|
sizeof(content) - 1U
|
||||||
|
);
|
||||||
|
|
||||||
|
if (written !=
|
||||||
|
(ssize_t)(sizeof(content) - 1U)) {
|
||||||
|
(void)close(fd);
|
||||||
|
(void)unlink(path);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (close(fd) != 0) {
|
||||||
|
(void)unlink(path);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
TrainlogUsbDevice
|
||||||
|
devices[DEVICE_CAPACITY];
|
||||||
|
|
||||||
|
TrainlogMtpStorage
|
||||||
|
storages[STORAGE_CAPACITY];
|
||||||
|
|
||||||
|
size_t device_count = 0U;
|
||||||
|
size_t storage_count = 0U;
|
||||||
|
uint32_t folder_id = 0U;
|
||||||
|
uint32_t item_id = 0U;
|
||||||
|
bool created = false;
|
||||||
|
char local_path[64];
|
||||||
|
TrainlogStatus status;
|
||||||
|
|
||||||
|
status =
|
||||||
|
trainlog_usb_list_mtp_devices(
|
||||||
|
devices,
|
||||||
|
DEVICE_CAPACITY,
|
||||||
|
&device_count
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK ||
|
||||||
|
device_count == 0U) {
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"No physical MTP device available.\n"
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
status =
|
||||||
|
trainlog_mtp_list_storages(
|
||||||
|
devices[0].bus_number,
|
||||||
|
devices[0].device_number,
|
||||||
|
storages,
|
||||||
|
STORAGE_CAPACITY,
|
||||||
|
&storage_count
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK ||
|
||||||
|
storage_count == 0U) {
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"No MTP storage available.\n"
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
"Using %s / %s\n",
|
||||||
|
devices[0].vendor,
|
||||||
|
devices[0].model
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
"Storage: %s (0x%08x)\n",
|
||||||
|
storages[0].description,
|
||||||
|
storages[0].storage_id
|
||||||
|
);
|
||||||
|
|
||||||
|
status =
|
||||||
|
trainlog_mtp_ensure_root_folder(
|
||||||
|
devices[0].bus_number,
|
||||||
|
devices[0].device_number,
|
||||||
|
storages[0].storage_id,
|
||||||
|
"Trainlog",
|
||||||
|
&folder_id,
|
||||||
|
&created
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"Unable to ensure Trainlog folder: %d\n",
|
||||||
|
(int)status
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
"Trainlog folder id=0x%08x (%s)\n",
|
||||||
|
folder_id,
|
||||||
|
created
|
||||||
|
? "created"
|
||||||
|
: "already present"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (create_probe_file(local_path) != 0) {
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"Unable to create local probe file.\n"
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
status =
|
||||||
|
trainlog_mtp_send_text_file(
|
||||||
|
devices[0].bus_number,
|
||||||
|
devices[0].device_number,
|
||||||
|
storages[0].storage_id,
|
||||||
|
folder_id,
|
||||||
|
local_path,
|
||||||
|
"trainlog-probe.txt",
|
||||||
|
&item_id
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)unlink(local_path);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"Unable to send probe file: %d\n",
|
||||||
|
(int)status
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
"Uploaded Trainlog/trainlog-probe.txt "
|
||||||
|
"item_id=0x%08x\n",
|
||||||
|
item_id
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
161
tui/tools/mtp_probe.c
Normal file
161
tui/tools/mtp_probe.c
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
/**
|
||||||
|
* @file mtp_probe.c
|
||||||
|
* @brief Manual end-to-end USB -> MTP storage probe.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "trainlog/mtp.h"
|
||||||
|
#include "trainlog/status.h"
|
||||||
|
#include "trainlog/usb.h"
|
||||||
|
|
||||||
|
#define DEVICE_CAPACITY 16U
|
||||||
|
#define STORAGE_CAPACITY 16U
|
||||||
|
|
||||||
|
static double bytes_to_gib(
|
||||||
|
uint64_t bytes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
(double)bytes /
|
||||||
|
(1024.0 * 1024.0 * 1024.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
TrainlogUsbDevice
|
||||||
|
devices[DEVICE_CAPACITY];
|
||||||
|
|
||||||
|
size_t device_count = 0U;
|
||||||
|
size_t device_index;
|
||||||
|
TrainlogStatus status;
|
||||||
|
|
||||||
|
status =
|
||||||
|
trainlog_usb_list_mtp_devices(
|
||||||
|
devices,
|
||||||
|
DEVICE_CAPACITY,
|
||||||
|
&device_count
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"USB MTP discovery failed: %d\n",
|
||||||
|
(int)status
|
||||||
|
);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
"Physical MTP devices: %zu\n",
|
||||||
|
device_count
|
||||||
|
);
|
||||||
|
|
||||||
|
for (device_index = 0U;
|
||||||
|
device_index < device_count;
|
||||||
|
++device_index) {
|
||||||
|
TrainlogMtpStorage
|
||||||
|
storages[STORAGE_CAPACITY];
|
||||||
|
|
||||||
|
const TrainlogUsbDevice *usb =
|
||||||
|
&devices[device_index];
|
||||||
|
|
||||||
|
size_t storage_count = 0U;
|
||||||
|
size_t storage_index;
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
"\n[%zu] %s / %s\n",
|
||||||
|
device_index,
|
||||||
|
usb->vendor[0] != '\0'
|
||||||
|
? usb->vendor
|
||||||
|
: "(unknown vendor)",
|
||||||
|
usb->model[0] != '\0'
|
||||||
|
? usb->model
|
||||||
|
: "(unknown model)"
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
" usb=%03u:%03u vid:pid=%04x:%04x\n",
|
||||||
|
usb->bus_number,
|
||||||
|
usb->device_number,
|
||||||
|
usb->vendor_id,
|
||||||
|
usb->product_id
|
||||||
|
);
|
||||||
|
|
||||||
|
status =
|
||||||
|
trainlog_mtp_list_storages(
|
||||||
|
usb->bus_number,
|
||||||
|
usb->device_number,
|
||||||
|
storages,
|
||||||
|
STORAGE_CAPACITY,
|
||||||
|
&storage_count
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
(void)printf(
|
||||||
|
" libmtp open/storage failed: %d\n",
|
||||||
|
(int)status
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
" storages=%zu\n",
|
||||||
|
storage_count
|
||||||
|
);
|
||||||
|
|
||||||
|
for (storage_index = 0U;
|
||||||
|
storage_index < storage_count;
|
||||||
|
++storage_index) {
|
||||||
|
const TrainlogMtpStorage *storage =
|
||||||
|
&storages[storage_index];
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
" storage[%zu] id=0x%08" PRIx32 "\n",
|
||||||
|
storage_index,
|
||||||
|
storage->storage_id
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
" description=%s\n",
|
||||||
|
storage->description[0] != '\0'
|
||||||
|
? storage->description
|
||||||
|
: "(none)"
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
" volume=%s\n",
|
||||||
|
storage->volume_identifier[0] != '\0'
|
||||||
|
? storage->volume_identifier
|
||||||
|
: "(none)"
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
" capacity=%.2f GiB free=%.2f GiB\n",
|
||||||
|
bytes_to_gib(
|
||||||
|
storage->max_capacity_bytes
|
||||||
|
),
|
||||||
|
bytes_to_gib(
|
||||||
|
storage->free_space_bytes
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
" access=0x%04x "
|
||||||
|
"storage_type=0x%04x "
|
||||||
|
"fs=0x%04x\n",
|
||||||
|
(unsigned int)
|
||||||
|
storage->access_capability,
|
||||||
|
(unsigned int)
|
||||||
|
storage->storage_type,
|
||||||
|
(unsigned int)
|
||||||
|
storage->filesystem_type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
273
tui/tools/mtp_roundtrip_probe.c
Normal file
273
tui/tools/mtp_roundtrip_probe.c
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "trainlog/mtp.h"
|
||||||
|
#include "trainlog/status.h"
|
||||||
|
#include "trainlog/usb.h"
|
||||||
|
|
||||||
|
#define DEVICE_CAPACITY 16U
|
||||||
|
#define STORAGE_CAPACITY 16U
|
||||||
|
#define ENTRY_CAPACITY 128U
|
||||||
|
|
||||||
|
static const char EXPECTED_CONTENT[] =
|
||||||
|
"Trainlog MTP write probe\n"
|
||||||
|
"If you can read this file on Android, USB/MTP write works.\n";
|
||||||
|
|
||||||
|
static int verify_file(
|
||||||
|
const char *path
|
||||||
|
)
|
||||||
|
{
|
||||||
|
FILE *stream;
|
||||||
|
char buffer[256];
|
||||||
|
size_t read_size;
|
||||||
|
|
||||||
|
stream =
|
||||||
|
fopen(
|
||||||
|
path,
|
||||||
|
"rb"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (stream == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
read_size =
|
||||||
|
fread(
|
||||||
|
buffer,
|
||||||
|
1U,
|
||||||
|
sizeof(buffer),
|
||||||
|
stream
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ferror(stream) != 0) {
|
||||||
|
(void)fclose(stream);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fclose(stream) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read_size !=
|
||||||
|
sizeof(EXPECTED_CONTENT) - 1U) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(
|
||||||
|
buffer,
|
||||||
|
EXPECTED_CONTENT,
|
||||||
|
read_size
|
||||||
|
) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
TrainlogUsbDevice
|
||||||
|
devices[DEVICE_CAPACITY];
|
||||||
|
|
||||||
|
TrainlogMtpStorage
|
||||||
|
storages[STORAGE_CAPACITY];
|
||||||
|
|
||||||
|
TrainlogMtpEntry
|
||||||
|
entries[ENTRY_CAPACITY];
|
||||||
|
|
||||||
|
size_t device_count = 0U;
|
||||||
|
size_t storage_count = 0U;
|
||||||
|
size_t entry_count = 0U;
|
||||||
|
size_t index;
|
||||||
|
uint32_t folder_id = 0U;
|
||||||
|
uint32_t probe_item_id = 0U;
|
||||||
|
bool created = false;
|
||||||
|
char local_path[] =
|
||||||
|
"/tmp/trainlog-mtp-readback-XXXXXX";
|
||||||
|
int fd;
|
||||||
|
TrainlogStatus status;
|
||||||
|
|
||||||
|
status =
|
||||||
|
trainlog_usb_list_mtp_devices(
|
||||||
|
devices,
|
||||||
|
DEVICE_CAPACITY,
|
||||||
|
&device_count
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK ||
|
||||||
|
device_count == 0U) {
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"No physical MTP device available.\n"
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
status =
|
||||||
|
trainlog_mtp_list_storages(
|
||||||
|
devices[0].bus_number,
|
||||||
|
devices[0].device_number,
|
||||||
|
storages,
|
||||||
|
STORAGE_CAPACITY,
|
||||||
|
&storage_count
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK ||
|
||||||
|
storage_count == 0U) {
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"No MTP storage available.\n"
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
status =
|
||||||
|
trainlog_mtp_ensure_root_folder(
|
||||||
|
devices[0].bus_number,
|
||||||
|
devices[0].device_number,
|
||||||
|
storages[0].storage_id,
|
||||||
|
"Trainlog",
|
||||||
|
&folder_id,
|
||||||
|
&created
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"Unable to locate Trainlog folder: %d\n",
|
||||||
|
(int)status
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
"Trainlog folder id=0x%08x (%s)\n",
|
||||||
|
folder_id,
|
||||||
|
created
|
||||||
|
? "created"
|
||||||
|
: "already present"
|
||||||
|
);
|
||||||
|
|
||||||
|
status =
|
||||||
|
trainlog_mtp_list_folder(
|
||||||
|
devices[0].bus_number,
|
||||||
|
devices[0].device_number,
|
||||||
|
storages[0].storage_id,
|
||||||
|
folder_id,
|
||||||
|
entries,
|
||||||
|
ENTRY_CAPACITY,
|
||||||
|
&entry_count
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"Unable to list Trainlog folder: %d\n",
|
||||||
|
(int)status
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
"Trainlog entries: %zu\n",
|
||||||
|
entry_count
|
||||||
|
);
|
||||||
|
|
||||||
|
for (index = 0U;
|
||||||
|
index < entry_count;
|
||||||
|
++index) {
|
||||||
|
(void)printf(
|
||||||
|
" %s id=0x%08x %s size=%llu\n",
|
||||||
|
entries[index].folder
|
||||||
|
? "[DIR] "
|
||||||
|
: "[FILE]",
|
||||||
|
entries[index].item_id,
|
||||||
|
entries[index].name,
|
||||||
|
(unsigned long long)
|
||||||
|
entries[index].size_bytes
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!entries[index].folder &&
|
||||||
|
strcmp(
|
||||||
|
entries[index].name,
|
||||||
|
"trainlog-probe.txt"
|
||||||
|
) == 0) {
|
||||||
|
probe_item_id =
|
||||||
|
entries[index].item_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (probe_item_id == 0U) {
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"trainlog-probe.txt not found.\n"
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd =
|
||||||
|
mkstemp(
|
||||||
|
local_path
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fd < 0) {
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"Unable to create local readback file.\n"
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (close(fd) != 0) {
|
||||||
|
(void)unlink(local_path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
status =
|
||||||
|
trainlog_mtp_receive_file(
|
||||||
|
devices[0].bus_number,
|
||||||
|
devices[0].device_number,
|
||||||
|
probe_item_id,
|
||||||
|
local_path
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
(void)unlink(local_path);
|
||||||
|
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"Unable to download probe file: %d\n",
|
||||||
|
(int)status
|
||||||
|
);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verify_file(
|
||||||
|
local_path
|
||||||
|
) != 0) {
|
||||||
|
(void)unlink(local_path);
|
||||||
|
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"Readback content mismatch.\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)unlink(
|
||||||
|
local_path
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
"ROUNDTRIP=PASS "
|
||||||
|
"Trainlog/trainlog-probe.txt\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
80
tui/tools/usb_probe.c
Normal file
80
tui/tools/usb_probe.c
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "trainlog/status.h"
|
||||||
|
#include "trainlog/usb.h"
|
||||||
|
|
||||||
|
#define PROBE_CAPACITY 16U
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
TrainlogUsbDevice
|
||||||
|
devices[PROBE_CAPACITY];
|
||||||
|
|
||||||
|
size_t count = 0U;
|
||||||
|
size_t index;
|
||||||
|
TrainlogStatus status;
|
||||||
|
|
||||||
|
status =
|
||||||
|
trainlog_usb_list_mtp_devices(
|
||||||
|
devices,
|
||||||
|
PROBE_CAPACITY,
|
||||||
|
&count
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != TRAINLOG_STATUS_OK) {
|
||||||
|
(void)fprintf(
|
||||||
|
stderr,
|
||||||
|
"MTP discovery failed: %d\n",
|
||||||
|
(int)status
|
||||||
|
);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
"MTP devices: %zu\n",
|
||||||
|
count
|
||||||
|
);
|
||||||
|
|
||||||
|
for (index = 0U;
|
||||||
|
index < count;
|
||||||
|
++index) {
|
||||||
|
const TrainlogUsbDevice *device =
|
||||||
|
&devices[index];
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
"[%zu] %s / %s\n",
|
||||||
|
index,
|
||||||
|
device->vendor[0] != '\0'
|
||||||
|
? device->vendor
|
||||||
|
: "(unknown vendor)",
|
||||||
|
device->model[0] != '\0'
|
||||||
|
? device->model
|
||||||
|
: "(unknown model)"
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
" usb=%03u:%03u vid:pid=%04x:%04x\n",
|
||||||
|
device->bus_number,
|
||||||
|
device->device_number,
|
||||||
|
device->vendor_id,
|
||||||
|
device->product_id
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
" serial=%s\n",
|
||||||
|
device->serial[0] != '\0'
|
||||||
|
? device->serial
|
||||||
|
: "(none)"
|
||||||
|
);
|
||||||
|
|
||||||
|
(void)printf(
|
||||||
|
" syspath=%s\n",
|
||||||
|
device->syspath[0] != '\0'
|
||||||
|
? device->syspath
|
||||||
|
: "(none)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue