Initial C API

This commit is contained in:
Paul Lietar 2016-01-26 23:21:57 +00:00
parent 327bb8477e
commit ae38e60518
8 changed files with 394 additions and 0 deletions

16
capi/Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "librespot_capi"
version = "0.1.0"
authors = ["Paul Liétar <paul@lietar.net>"]
[lib]
name = "librespot_capi"
crate-type = ["staticlib"]
[dependencies]
libc = "0.2"
eventual = "~0.1.5"
owning_ref = "0.1.*"
[dependencies.librespot]
path = "../"

33
capi/src/artist.rs Normal file
View file

@ -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<Artist>;
#[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
}

17
capi/src/lib.rs Normal file
View file

@ -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::*;

36
capi/src/link.rs Normal file
View file

@ -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<Link>;
#[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))
}

54
capi/src/metadata.rs Normal file
View file

@ -0,0 +1,54 @@
use eventual::Async;
use owning_ref::MutexGuardRef;
use std::sync::{Mutex, Arc};
use librespot::metadata::{MetadataTrait, MetadataRef};
pub enum SpMetadataInner<T: MetadataTrait> {
Loading,
Error,
Loaded(T),
}
pub struct SpMetadata<T: MetadataTrait>(Arc<Mutex<SpMetadataInner<T>>>);
impl <T: MetadataTrait> SpMetadata<T> {
pub fn from_future(future: MetadataRef<T>) -> SpMetadata<T> {
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<MutexGuardRef<SpMetadataInner<T>, 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,
}
}
}

110
capi/src/session.rs Normal file
View file

@ -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::<u8>(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
}

60
capi/src/track.rs Normal file
View file

@ -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<Track>;
#[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>(artist_id));
Box::into_raw(Box::new(artist))
})
.unwrap_or(null_mut())
}

68
capi/src/types.rs Normal file
View file

@ -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,
}