pub mod devices; pub mod error; pub mod ptp; use std::{io::Cursor, time::Duration}; use anyhow::{anyhow, bail}; use byteorder::{LittleEndian, WriteBytesExt}; use devices::SupportedCamera; use log::{debug, error}; use ptp::{ Ptp, enums::{CommandCode, PropCode, UsbMode}, structs::DeviceInfo, }; use rusb::{GlobalContext, constants::LIBUSB_CLASS_IMAGE}; const SESSION: u32 = 1; pub struct Camera { r#impl: Box>, ptp: Ptp, } impl Camera { pub fn from_device(device: &rusb::Device) -> anyhow::Result { for supported_camera in devices::SUPPORTED { if let Ok(r#impl) = supported_camera.new_camera(device) { let bus = device.bus_number(); let address = device.address(); let config_descriptor = device.active_config_descriptor()?; let interface_descriptor = config_descriptor .interfaces() .flat_map(|i| i.descriptors()) .find(|x| x.class_code() == LIBUSB_CLASS_IMAGE) .ok_or(rusb::Error::NotFound)?; let interface = interface_descriptor.interface_number(); debug!("Found interface {interface}"); let handle = device.open()?; handle.claim_interface(interface)?; let bulk_in = Self::find_endpoint( &interface_descriptor, rusb::Direction::In, rusb::TransferType::Bulk, )?; let bulk_out = Self::find_endpoint( &interface_descriptor, rusb::Direction::Out, rusb::TransferType::Bulk, )?; let transaction_id = 0; let chunk_size = r#impl.chunk_size(); let mut ptp = Ptp { bus, address, interface, bulk_in, bulk_out, handle, transaction_id, chunk_size, }; debug!("Opening session"); let () = r#impl.open_session(&mut ptp, SESSION)?; debug!("Session opened"); return Ok(Self { r#impl, ptp }); } } bail!("Device not supported"); } fn find_endpoint( interface_descriptor: &rusb::InterfaceDescriptor<'_>, direction: rusb::Direction, transfer_type: rusb::TransferType, ) -> Result { interface_descriptor .endpoint_descriptors() .find(|ep| ep.direction() == direction && ep.transfer_type() == transfer_type) .map(|x| x.address()) .ok_or(rusb::Error::NotFound) } pub fn name(&self) -> &'static str { self.r#impl.supported_camera().name } pub fn vendor_id(&self) -> u16 { self.r#impl.supported_camera().vendor } pub fn product_id(&self) -> u16 { self.r#impl.supported_camera().product } pub fn connected_usb_id(&self) -> String { format!("{}.{}", self.ptp.bus, self.ptp.address) } fn prop_value_as_scalar(data: &[u8]) -> anyhow::Result { let data = match data.len() { 1 => anyhow::Ok(u32::from(data[0])), 2 => anyhow::Ok(u32::from(u16::from_le_bytes([data[0], data[1]]))), 4 => anyhow::Ok(u32::from_le_bytes([data[0], data[1], data[2], data[3]])), n => bail!("Cannot parse {n} bytes as scalar"), }?; Ok(data) } pub fn get_info(&mut self) -> anyhow::Result { let info = self.r#impl.get_info(&mut self.ptp)?; Ok(info) } pub fn get_usb_mode(&mut self) -> anyhow::Result { let data = self .r#impl .get_prop_value(&mut self.ptp, PropCode::FujiUsbMode); let result = Self::prop_value_as_scalar(&data?)?.into(); Ok(result) } pub fn get_battery_info(&mut self) -> anyhow::Result { let data = self .r#impl .get_prop_value(&mut self.ptp, PropCode::FujiBatteryInfo2); let data = data?; debug!("Raw battery data: {data:?}"); let utf16: Vec = data[1..] .chunks(2) .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) .take_while(|&c| c != 0) .collect(); let utf8_string = String::from_utf16(&utf16)?; debug!("Decoded UTF-16 string: {utf8_string}"); let percentage: u32 = utf8_string .split(',') .next() .ok_or_else(|| anyhow!("Failed to parse battery percentage"))? .parse()?; Ok(percentage) } pub fn export_backup(&mut self) -> anyhow::Result> { self.r#impl.export_backup(&mut self.ptp) } pub fn import_backup(&mut self, backup: &[u8]) -> anyhow::Result<()> { self.r#impl.import_backup(&mut self.ptp, backup) } } impl Drop for Camera { fn drop(&mut self) { debug!("Closing session"); if let Err(e) = self.r#impl.close_session(&mut self.ptp, SESSION) { error!("Error closing session: {e}"); } debug!("Session closed"); } } pub trait CameraImpl { fn supported_camera(&self) -> &'static SupportedCamera

; fn timeout(&self) -> Duration { Duration::default() } fn chunk_size(&self) -> usize { 1024 * 1024 } fn open_session(&self, ptp: &mut Ptp, session_id: u32) -> anyhow::Result<()> { debug!("Sending OpenSession command"); _ = ptp.send( CommandCode::OpenSession, Some(&[session_id]), None, true, self.timeout(), )?; Ok(()) } fn close_session(&self, ptp: &mut Ptp, _: u32) -> anyhow::Result<()> { debug!("Sending CloseSession command"); _ = ptp.send(CommandCode::CloseSession, None, None, true, self.timeout())?; Ok(()) } fn get_info(&self, ptp: &mut Ptp) -> anyhow::Result { debug!("Sending GetDeviceInfo command"); let response = ptp.send(CommandCode::GetDeviceInfo, None, None, true, self.timeout())?; debug!("Received response with {} bytes", response.len()); let info = DeviceInfo::try_from(response.as_slice())?; Ok(info) } fn get_prop_value(&self, ptp: &mut Ptp, prop: PropCode) -> anyhow::Result> { debug!("Sending GetDevicePropValue command for property {prop:?}"); let response = ptp.send( CommandCode::GetDevicePropValue, Some(&[prop as u32]), None, true, self.timeout(), )?; debug!("Received response with {} bytes", response.len()); Ok(response) } fn export_backup(&self, ptp: &mut Ptp) -> anyhow::Result> { const HANDLE: u32 = 0x0; debug!("Sending GetObjectInfo command for backup"); let response = ptp.send( CommandCode::GetObjectInfo, Some(&[HANDLE]), None, true, self.timeout(), )?; debug!("Received response with {} bytes", response.len()); debug!("Sending GetObject command for backup"); let response = ptp.send( CommandCode::GetObject, Some(&[HANDLE]), None, true, self.timeout(), )?; debug!("Received response with {} bytes", response.len()); Ok(response) } fn import_backup(&self, ptp: &mut Ptp, buffer: &[u8]) -> anyhow::Result<()> { debug!("Preparing ObjectInfo header for backup"); let mut header1 = vec![0u8; 1012]; let mut cursor = Cursor::new(&mut header1[..]); cursor.write_u32::(0x0)?; cursor.write_u16::(0x5000)?; cursor.write_u16::(0x0)?; cursor.write_u32::(u32::try_from(buffer.len())?)?; let header2 = vec![0u8; 64]; debug!("Sending SendObjectInfo command for backup"); let response = ptp.send_many( CommandCode::SendObjectInfo, Some(&[0x0, 0x0]), Some(&[&header1, &header2]), true, self.timeout(), )?; debug!("Received response with {} bytes", response.len()); debug!("Sending SendObject command for backup"); let response = ptp.send( CommandCode::SendObject, Some(&[0x0]), Some(buffer), true, self.timeout(), )?; debug!("Received response with {} bytes", response.len()); Ok(()) } }