A tool to sync music with your favorite devices
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

gpod: speak ipod protocol baybeeee

Gee Sawra cee02340 9dbd8aa4

+350 -2
+32 -2
build.rs
··· 1 + use std::path::PathBuf; 2 + 3 + use pkg_config::Library; 4 + 1 5 fn main() { 2 - println!("cargo:rerun-if-changed=database/migrations/local"); 3 - println!("cargo:rerun-if-changed=database/migrations/destination"); 6 + let library = pkg_config::Config::new() 7 + .probe("libgpod-1.0") 8 + .expect("Could not find libgpod-1.0 using pkg-config"); 9 + 10 + println!("cargo:rerun-if-changed=src/gpod/wrapper.h"); 11 + println!("cargo:rerun-if-changed=build.rs"); 12 + println!("cargo:rerun-if-changed=src/db/migrations"); 13 + 14 + libgpod_bindgen(&library); 15 + } 16 + 17 + fn libgpod_bindgen(library: &Library) { 18 + let library = library.clone(); 19 + 20 + let mut builder = bindgen::Builder::default() 21 + .header("src/gpod/wrapper.h") 22 + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())); 23 + 24 + for path in library.include_paths { 25 + builder = builder.clang_arg(format!("-I{}", path.display())); 26 + } 27 + 28 + let bindings = builder.generate().expect("Unable to generate bindings"); 29 + 30 + let out_path = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 31 + bindings 32 + .write_to_file(out_path.join("bindings.rs")) 33 + .expect("Couldn't write bindings!"); 4 34 }
+306
src/gpod/gpod.rs
··· 1 + use std::{ffi::CString, os::linux::fs::MetadataExt, path::PathBuf, ptr}; 2 + 3 + use crate::model::Track; 4 + 5 + use super::bindings::*; 6 + use anyhow::{Result, anyhow}; 7 + use glib::gobject_ffi::g_type_init; 8 + 9 + pub struct IPodHandle { 10 + db_ptr: *mut Itdb_iTunesDB, 11 + device_ptr: *mut Itdb_Device, 12 + } 13 + 14 + // impl Drop for IPodHandle { 15 + // fn drop(&mut self) { 16 + // unsafe { 17 + // itdb_free(self.db_ptr); 18 + // itdb_device_free(self.device_ptr); 19 + // } 20 + // } 21 + // } 22 + 23 + pub struct CopyProcess { 24 + db_ptr: *mut Itdb_iTunesDB, 25 + } 26 + 27 + impl Drop for CopyProcess { 28 + fn drop(&mut self) { 29 + unsafe { 30 + let mut error: *mut GError = ptr::null_mut(); 31 + log::debug!("itdb_write..."); 32 + let write_success = itdb_write(self.db_ptr, &mut error); 33 + log::debug!("itdb_write done!"); 34 + if write_success != 1 { 35 + if error.is_null() { 36 + panic!("write to iPod: unknown error") 37 + } 38 + 39 + let error = *error; 40 + let err_msg = std::ffi::CString::from_raw(error.message); 41 + panic!( 42 + "initialization error, code {}: {}", 43 + error.code, 44 + err_msg.to_string_lossy() 45 + ); 46 + } 47 + itdb_stop_sync(self.db_ptr); 48 + } 49 + } 50 + } 51 + 52 + impl CopyProcess { 53 + pub fn copy_file( 54 + &self, 55 + track: &Track, 56 + file: PathBuf, 57 + codec: String, 58 + sample_rate: u16, 59 + bitrate: i32, 60 + ) -> Result<()> { 61 + unsafe { 62 + let ipod_track = itdb_track_new(); 63 + 64 + (*ipod_track).title = c_strdup(&track.title); 65 + (*ipod_track).album = c_strdup(&track.album); 66 + (*ipod_track).artist = c_strdup(&track.artist); 67 + (*ipod_track).filetype = c_strdup(&format!("{}-file", codec.to_uppercase())); 68 + (*ipod_track).genre = c_strdup(&track.genre); 69 + (*ipod_track).comment = c_strdup(""); 70 + (*ipod_track).tracklen = track.track_len as i32; 71 + (*ipod_track).track_nr = track.number.try_into().unwrap_or(0); 72 + (*ipod_track).samplerate = sample_rate; 73 + (*ipod_track).bitrate = bitrate * 1000; 74 + (*ipod_track).year = track.year as i32; 75 + (*ipod_track).size = file.metadata().unwrap().st_size().try_into().unwrap(); 76 + 77 + let mut error: *mut GError = ptr::null_mut(); 78 + let path = CString::new(file.display().to_string()).unwrap(); 79 + itdb_track_add(self.db_ptr, ipod_track, -1); 80 + itdb_playlist_add_track(itdb_playlist_mpl(self.db_ptr), ipod_track, -1); 81 + let copy_success = itdb_cp_track_to_ipod(ipod_track, path.as_ptr(), &mut error); 82 + if copy_success != 1 { 83 + itdb_track_free(ipod_track); 84 + 85 + if error.is_null() { 86 + return Err(anyhow!("copy to iPod: unknown error")); 87 + } 88 + 89 + let error = *error; 90 + let err_msg = std::ffi::CString::from_raw(error.message); 91 + return Err(anyhow!( 92 + "initialization error, code {}: {}", 93 + error.code, 94 + err_msg.to_string_lossy() 95 + )); 96 + } 97 + 98 + Ok(()) 99 + } 100 + } 101 + } 102 + 103 + impl IPodHandle { 104 + pub fn initialize(path: String, model: String) -> Result<()> { 105 + unsafe { 106 + g_type_init(); 107 + let mountpoint = std::ffi::CString::new(path).unwrap(); 108 + let model = std::ffi::CString::new(model).unwrap(); 109 + 110 + let mut error: *mut GError = ptr::null_mut(); 111 + if itdb_init_ipod(mountpoint.as_ptr(), model.as_ptr(), ptr::null(), &mut error) != 1 { 112 + if error.is_null() { 113 + return Err(anyhow!("could not initialize ipod, unknown error")); 114 + } 115 + 116 + let error = *error; 117 + let err_msg = std::ffi::CString::from_raw(error.message); 118 + return Err(anyhow!( 119 + "initialization error, code {}: {}", 120 + error.code, 121 + err_msg.to_string_lossy() 122 + )); 123 + } 124 + 125 + let serials = rusb::devices()? 126 + .iter() 127 + .filter(|d| { 128 + let dd = d.device_descriptor(); 129 + let d = d.open(); 130 + if d.is_err() { 131 + return false; 132 + } 133 + let d = d.unwrap(); 134 + 135 + if let Ok(dd) = dd { 136 + match dd.serial_number_string_index() { 137 + Some(_) => (), 138 + None => return false, 139 + }; 140 + 141 + let product_str = d.read_product_string_ascii(&dd).unwrap(); 142 + let serial_str = d.read_serial_number_string_ascii(&dd).unwrap(); 143 + 144 + return dd.vendor_id() == 0x05ac 145 + && product_str == "iPod" 146 + && !serial_str.is_empty(); 147 + } else { 148 + return false; 149 + } 150 + }) 151 + .map(|d| { 152 + let d = d.open().unwrap(); 153 + let dd = d.device().device_descriptor().unwrap(); 154 + 155 + d.read_serial_number_string_ascii(&dd).unwrap() 156 + }) 157 + .collect::<Vec<String>>(); 158 + 159 + match serials.len() { 160 + 0 => return Err(anyhow!("no iPod found")), 161 + 1 => { 162 + let device_ptr = itdb_device_new(); 163 + itdb_device_set_mountpoint(device_ptr, mountpoint.as_ptr()); 164 + let fw_guid_field = CString::new("FirewireGuid").unwrap(); 165 + let serial = 166 + CString::new(format!("0x{}", &serials.first().unwrap()[..16])).unwrap(); 167 + 168 + // FirewireGuid 169 + itdb_device_set_sysinfo(device_ptr, fw_guid_field.as_ptr(), serial.as_ptr()); 170 + let mut error: *mut GError = ptr::null_mut(); 171 + if itdb_device_write_sysinfo(device_ptr, &mut error) != 1 { 172 + if error.is_null() { 173 + return Err(anyhow!( 174 + "could not initialize write sysinfo, unknown error" 175 + )); 176 + } 177 + 178 + let error = *error; 179 + let err_msg = std::ffi::CString::from_raw(error.message); 180 + return Err(anyhow!( 181 + "write sysinfo error, code {}: {}", 182 + error.code, 183 + err_msg.to_string_lossy() 184 + )); 185 + } 186 + } 187 + _ => return Err(anyhow!("more than 1 iPod found, please connect just one")), 188 + } 189 + } 190 + 191 + Ok(()) 192 + } 193 + pub fn new(path: String) -> Result<Self> { 194 + unsafe { 195 + g_type_init(); 196 + let mountpoint = std::ffi::CString::new(path).unwrap(); 197 + 198 + let device_ptr = itdb_device_new(); 199 + itdb_device_set_mountpoint(device_ptr, mountpoint.as_ptr()); 200 + if itdb_device_read_sysinfo(device_ptr) != 1 { 201 + return Err(anyhow!("could not read sysinfo from iPod")); 202 + }; 203 + 204 + let mut error: *mut GError = ptr::null_mut(); 205 + let db_ptr = itdb_parse(mountpoint.as_ptr(), &mut error); 206 + if !error.is_null() { 207 + let error = *error; 208 + let err_msg = std::ffi::CString::from_raw(error.message); 209 + return Err(anyhow!( 210 + "read ipod database, code {}: {}", 211 + error.code, 212 + err_msg.to_string_lossy() 213 + )); 214 + } 215 + 216 + match db_ptr.is_null() { 217 + true => Err(anyhow!("no database found")), 218 + false => Ok(Self { db_ptr, device_ptr }), 219 + } 220 + } 221 + } 222 + pub fn info(&self) { 223 + unsafe { 224 + let pi = itdb_device_get_sysinfo( 225 + self.device_ptr, 226 + CString::new("ModelNumStr").unwrap().as_ptr(), 227 + ); 228 + 229 + let asd = CString::from_raw(pi); 230 + println!("numstr: {}", asd.to_string_lossy()); 231 + 232 + let pi = itdb_device_get_sysinfo( 233 + self.device_ptr, 234 + CString::new("FirewireGuid").unwrap().as_ptr(), 235 + ); 236 + 237 + if pi.is_null() { 238 + println!("could not read FirewireGuid"); 239 + return; 240 + } 241 + 242 + let asd = CString::from_raw(pi); 243 + println!("numstr: {}", asd.to_string_lossy()); 244 + } 245 + } 246 + 247 + pub fn copy_process(&self) -> CopyProcess { 248 + unsafe { 249 + itdb_start_sync(self.db_ptr); 250 + } 251 + CopyProcess { 252 + db_ptr: self.db_ptr, 253 + } 254 + } 255 + } 256 + 257 + unsafe fn c_strdup(s: &str) -> *mut std::ffi::c_char { 258 + unsafe { 259 + let c_str = std::ffi::CString::new(s).unwrap(); 260 + // libc::strdup allocates memory (malloc) that libgpod can safely read and eventually free. 261 + libc::strdup(c_str.as_ptr()) 262 + } 263 + } 264 + 265 + #[cfg(test)] 266 + mod tests { 267 + use std::str::FromStr; 268 + 269 + use super::*; 270 + 271 + #[test] 272 + fn initialize() { 273 + IPodHandle::initialize("/run/media/geesawra/IPOD".to_owned(), "MA003FB".to_owned()) 274 + .unwrap(); 275 + let ih = IPodHandle::new("/run/media/geesawra/IPOD".to_owned()).unwrap(); 276 + ih.info(); 277 + } 278 + 279 + #[test] 280 + fn copy() { 281 + let ih = IPodHandle::new("/run/media/geesawra/IPOD".to_owned()).unwrap(); 282 + let p = ih.copy_process(); 283 + 284 + let t = Track { 285 + title: "Marinmba".to_owned(), 286 + album: "Hit It!".to_owned(), 287 + artist: "Vianova".to_owned(), 288 + number: 42, 289 + ..Default::default() 290 + }; 291 + 292 + p.copy_file( 293 + &t, 294 + PathBuf::from_str( 295 + "/home/geesawra/Code/tracksync/dest/Vianova/Hit It!/1/Marimba.m4a.m4a", 296 + ) 297 + .unwrap(), 298 + "aac".to_owned(), 299 + 44100, 300 + 128, 301 + ) 302 + .unwrap(); 303 + 304 + drop(p); 305 + } 306 + }
+11
src/gpod/mod.rs
··· 1 + mod bindings { 2 + #![allow(non_upper_case_globals)] 3 + #![allow(non_camel_case_types)] 4 + #![allow(non_snake_case)] 5 + #![allow(dead_code)] 6 + #![allow(clippy::all)] 7 + 8 + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 9 + } 10 + 11 + pub mod gpod;
+1
src/gpod/wrapper.h
··· 1 + #include <gpod/itdb.h>