diff --git a/capi/Cargo.toml b/capi/Cargo.toml new file mode 100644 index 00000000..6782ef2d --- /dev/null +++ b/capi/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "librespot_capi" +version = "0.1.0" +authors = ["Paul LiƩtar "] + +[lib] +name = "librespot_capi" +crate-type = ["staticlib"] + +[dependencies] +libc = "0.2" +eventual = "~0.1.5" +owning_ref = "0.1.*" + +[dependencies.librespot] +path = "../" diff --git a/capi/src/artist.rs b/capi/src/artist.rs new file mode 100644 index 00000000..5bc5b42a --- /dev/null +++ b/capi/src/artist.rs @@ -0,0 +1,33 @@ +use libc::c_char; +use std::ffi::CString; +use std::mem; + +use librespot::metadata::Artist; + +use metadata::SpMetadata; + +#[allow(non_camel_case_types)] +pub type sp_artist = SpMetadata; + +#[no_mangle] +pub unsafe extern "C" fn sp_artist_is_loaded(c_artist: *mut sp_artist) -> bool { + let artist = &*c_artist; + artist.is_loaded() +} + +#[no_mangle] +pub unsafe extern "C" fn sp_artist_name(c_artist: *mut sp_artist) -> *const c_char { + let artist = &*c_artist; + + let name = artist.get() + .map(|metadata| metadata.name.clone()) + .unwrap_or("".to_owned()); + + let name = CString::new(name).unwrap(); + let c_name = name.as_ptr(); + + // FIXME + mem::forget(name); + + c_name +} diff --git a/capi/src/lib.rs b/capi/src/lib.rs new file mode 100644 index 00000000..5b600fe0 --- /dev/null +++ b/capi/src/lib.rs @@ -0,0 +1,17 @@ +extern crate librespot; +extern crate libc; +extern crate eventual; +extern crate owning_ref; + +pub mod artist; +pub mod link; +pub mod metadata; +pub mod session; +pub mod track; +mod types; + +pub use types::sp_session_config; +pub use types::sp_error; +pub use types::sp_error::*; + + diff --git a/capi/src/link.rs b/capi/src/link.rs new file mode 100644 index 00000000..a57fda07 --- /dev/null +++ b/capi/src/link.rs @@ -0,0 +1,36 @@ +use metadata::SpMetadata; +use session::global_session; +use track::sp_track; +use types::sp_error; +use types::sp_error::*; +use std::ffi::CStr; +use std::rc::Rc; +use libc::c_char; +use librespot::link::Link; + +#[allow(non_camel_case_types)] +pub type sp_link = Rc; + +#[no_mangle] +pub unsafe extern "C" fn sp_link_create_from_string(uri: *const c_char) -> *mut sp_link { + let uri = CStr::from_ptr(uri).to_string_lossy(); + let link = Link::from_str(&uri).unwrap(); + + Box::into_raw(Box::new(Rc::new(link))) +} + +#[no_mangle] +pub unsafe extern "C" fn sp_link_release(c_link: *mut sp_link) -> sp_error { + drop(Box::from_raw(c_link)); + + SP_ERROR_OK +} + +#[no_mangle] +pub unsafe extern "C" fn sp_link_as_track(c_link: *mut sp_link) -> *mut sp_track { + let link = &*c_link; + let session = &*global_session.unwrap(); + + let track = SpMetadata::from_future(link.as_track(session).unwrap()); + Box::into_raw(Box::new(track)) +} diff --git a/capi/src/metadata.rs b/capi/src/metadata.rs new file mode 100644 index 00000000..60175232 --- /dev/null +++ b/capi/src/metadata.rs @@ -0,0 +1,54 @@ +use eventual::Async; +use owning_ref::MutexGuardRef; +use std::sync::{Mutex, Arc}; + +use librespot::metadata::{MetadataTrait, MetadataRef}; + +pub enum SpMetadataInner { + Loading, + Error, + Loaded(T), +} + +pub struct SpMetadata(Arc>>); + +impl SpMetadata { + pub fn from_future(future: MetadataRef) -> SpMetadata { + let metadata = Arc::new(Mutex::new(SpMetadataInner::Loading)); + + { + let metadata = metadata.clone(); + future.receive(move |result| { + //let metadata = metadata.upgrade().unwrap(); + let mut metadata = metadata.lock().unwrap(); + + *metadata = match result { + Ok(data) => SpMetadataInner::Loaded(data), + Err(_) => SpMetadataInner::Error, + }; + }); + } + + SpMetadata(metadata) + } + + pub fn is_loaded(&self) -> bool { + self.get().is_some() + } + + pub fn get(&self) -> Option, T>> { + let inner = self.0.lock().unwrap(); + + match *inner { + SpMetadataInner::Loaded(_) => { + Some(MutexGuardRef::new(inner).map(|inner| { + match *inner { + SpMetadataInner::Loaded(ref metadata) => metadata, + _ => unreachable!(), + } + })) + } + _ => None, + } + } +} diff --git a/capi/src/session.rs b/capi/src/session.rs new file mode 100644 index 00000000..3381c931 --- /dev/null +++ b/capi/src/session.rs @@ -0,0 +1,110 @@ +use libc::{c_int, c_char}; +use std::ffi::{CStr, CString}; +use std::mem; +use std::slice::from_raw_parts; + +use librespot::session::{Session, Config, Bitrate}; + +use types::sp_error; +use types::sp_error::*; +use types::sp_session_config; + +pub static mut global_session: Option<*mut Session> = None; + +#[allow(non_camel_case_types)] +pub type sp_session = Session; + +#[no_mangle] +pub unsafe extern "C" fn sp_session_create(c_config: *const sp_session_config, + c_session: *mut *mut sp_session) -> sp_error { + assert_eq!(global_session, None); + + let c_config = &*c_config; + + let application_key = from_raw_parts::(c_config.application_key as *const u8, + c_config.application_key_size); + + let user_agent = CStr::from_ptr(c_config.user_agent).to_string_lossy().into_owned(); + let device_name = CStr::from_ptr(c_config.device_id).to_string_lossy().into_owned(); + let cache_location = CStr::from_ptr(c_config.cache_location).to_string_lossy().into_owned(); + + let config = Config { + application_key: application_key.to_owned(), + user_agent: user_agent, + device_name: device_name, + cache_location: cache_location.into(), + bitrate: Bitrate::Bitrate160, + }; + + let session = Box::new(Session::new(config)); + let session = Box::into_raw(session); + + global_session = Some(session); + *c_session = session; + + SP_ERROR_OK +} + +#[no_mangle] +pub unsafe extern "C" fn sp_session_release(c_session: *mut sp_session) -> sp_error { + assert_eq!(global_session, Some(c_session)); + + global_session = None; + drop(Box::from_raw(c_session)); + + SP_ERROR_OK +} + +#[no_mangle] +pub unsafe extern "C" fn sp_session_login(c_session: *mut sp_session, + c_username: *const c_char, + c_password: *const c_char, + _remember_me: bool, + _blob: *const c_char) -> sp_error { + assert_eq!(global_session, Some(c_session)); + + let session = &*c_session; + + let username = CStr::from_ptr(c_username).to_string_lossy().into_owned(); + let password = CStr::from_ptr(c_password).to_string_lossy().into_owned(); + + session.login_password(username, password).unwrap(); + + { + let session = session.clone(); + ::std::thread::spawn(move || { + loop { + session.poll(); + } + }); + } + + SP_ERROR_OK +} + +#[no_mangle] +pub unsafe extern "C" fn sp_session_user_name(c_session: *mut sp_session) -> *const c_char { + assert_eq!(global_session, Some(c_session)); + + let session = &*c_session; + + let username = CString::new(session.username()).unwrap(); + let c_username = username.as_ptr(); + + // FIXME + mem::forget(username); + + c_username +} + +#[no_mangle] +pub unsafe extern "C" fn sp_session_user_country(c_session: *mut sp_session) -> c_int { + assert_eq!(global_session, Some(c_session)); + + let session = &*c_session; + + let country = session.username(); + country.chars().fold(0, |acc, x| { + acc << 8 | (x as u32) + }) as c_int +} diff --git a/capi/src/track.rs b/capi/src/track.rs new file mode 100644 index 00000000..2910419c --- /dev/null +++ b/capi/src/track.rs @@ -0,0 +1,60 @@ +use libc::{c_int, c_char}; +use std::ffi::CString; +use std::mem; +use std::ptr::null_mut; + +use artist::sp_artist; +use metadata::SpMetadata; +use session::global_session; + +use librespot::metadata::{Track, Artist}; + +#[allow(non_camel_case_types)] +pub type sp_track = SpMetadata; + +#[no_mangle] +pub unsafe extern "C" fn sp_track_is_loaded(c_track: *mut sp_track) -> bool { + let track = &*c_track; + track.is_loaded() +} + +#[no_mangle] +pub unsafe extern "C" fn sp_track_name(c_track: *mut sp_track) -> *const c_char { + let track = &*c_track; + + let name = track.get() + .map(|metadata| metadata.name.clone()) + .unwrap_or("".to_owned()); + + let name = CString::new(name).unwrap(); + let c_name = name.as_ptr(); + + // FIXME + mem::forget(name); + + c_name +} + +#[no_mangle] +pub unsafe extern "C" fn sp_track_num_artists(c_track: *mut sp_track) -> c_int { + let track = &*c_track; + + track.get() + .map(|metadata| metadata.artists.len() as c_int) + .unwrap_or(0) +} + +#[no_mangle] +pub unsafe extern "C" fn sp_track_artist(c_track: *mut sp_track, index: c_int) -> *mut sp_artist { + let track = &*c_track; + let session = &*global_session.unwrap(); + + track.get() + .and_then(|metadata| metadata.artists.get(index as usize).map(|x| *x)) + .map(|artist_id| { + let artist = SpMetadata::from_future(session.metadata::(artist_id)); + Box::into_raw(Box::new(artist)) + }) + .unwrap_or(null_mut()) +} + diff --git a/capi/src/types.rs b/capi/src/types.rs new file mode 100644 index 00000000..6387000f --- /dev/null +++ b/capi/src/types.rs @@ -0,0 +1,68 @@ +#![allow(non_camel_case_types)] + +use libc::size_t; + +pub enum sp_session_callbacks {} + +#[derive(Clone, Copy)] +#[repr(u32)] +pub enum sp_error { + SP_ERROR_OK = 0, + SP_ERROR_BAD_API_VERSION = 1, + SP_ERROR_API_INITIALIZATION_FAILED = 2, + SP_ERROR_TRACK_NOT_PLAYABLE = 3, + SP_ERROR_BAD_APPLICATION_KEY = 5, + SP_ERROR_BAD_USERNAME_OR_PASSWORD = 6, + SP_ERROR_USER_BANNED = 7, + SP_ERROR_UNABLE_TO_CONTACT_SERVER = 8, + SP_ERROR_CLIENT_TOO_OLD = 9, + SP_ERROR_OTHER_PERMANENT = 10, + SP_ERROR_BAD_USER_AGENT = 11, + SP_ERROR_MISSING_CALLBACK = 12, + SP_ERROR_INVALID_INDATA = 13, + SP_ERROR_INDEX_OUT_OF_RANGE = 14, + SP_ERROR_USER_NEEDS_PREMIUM = 15, + SP_ERROR_OTHER_TRANSIENT = 16, + SP_ERROR_IS_LOADING = 17, + SP_ERROR_NO_STREAM_AVAILABLE = 18, + SP_ERROR_PERMISSION_DENIED = 19, + SP_ERROR_INBOX_IS_FULL = 20, + SP_ERROR_NO_CACHE = 21, + SP_ERROR_NO_SUCH_USER = 22, + SP_ERROR_NO_CREDENTIALS = 23, + SP_ERROR_NETWORK_DISABLED = 24, + SP_ERROR_INVALID_DEVICE_ID = 25, + SP_ERROR_CANT_OPEN_TRACE_FILE = 26, + SP_ERROR_APPLICATION_BANNED = 27, + SP_ERROR_OFFLINE_TOO_MANY_TRACKS = 31, + SP_ERROR_OFFLINE_DISK_CACHE = 32, + SP_ERROR_OFFLINE_EXPIRED = 33, + SP_ERROR_OFFLINE_NOT_ALLOWED = 34, + SP_ERROR_OFFLINE_LICENSE_LOST = 35, + SP_ERROR_OFFLINE_LICENSE_ERROR = 36, + SP_ERROR_LASTFM_AUTH_ERROR = 39, + SP_ERROR_INVALID_ARGUMENT = 40, + SP_ERROR_SYSTEM_FAILURE = 41, +} + +#[repr(C)] +#[derive(Copy,Clone)] +pub struct sp_session_config { + pub api_version: ::std::os::raw::c_int, + pub cache_location: *const ::std::os::raw::c_char, + pub settings_location: *const ::std::os::raw::c_char, + pub application_key: *const ::std::os::raw::c_void, + pub application_key_size: size_t, + pub user_agent: *const ::std::os::raw::c_char, + pub callbacks: *const sp_session_callbacks, + pub userdata: *mut ::std::os::raw::c_void, + pub compress_playlists: bool, + pub dont_save_metadata_for_playlists: bool, + pub initially_unload_playlists: bool, + pub device_id: *const ::std::os::raw::c_char, + pub proxy: *const ::std::os::raw::c_char, + pub proxy_username: *const ::std::os::raw::c_char, + pub proxy_password: *const ::std::os::raw::c_char, + pub tracefile: *const ::std::os::raw::c_char, +} +