musikr: use no_std in crate
Doesn't really help since jni uses std excessively.
This commit is contained in:
parent
c115e34aac
commit
7f84349f2e
7 changed files with 135 additions and 161 deletions
|
@ -8,8 +8,8 @@ name = "metadatajni"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
cxx = "1.0.137"
|
||||
jni = "0.21.1"
|
||||
cxx = { version = "1.0.137", default-features = false, features = ["alloc"] }
|
||||
jni = { version = "0.21.1", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
cxx-build = "1.0.137"
|
|
@ -8,12 +8,12 @@ extern "C"
|
|||
{
|
||||
const char *rust_stream_name(const void *stream);
|
||||
size_t rust_stream_read(void *stream, uint8_t *buffer, size_t length);
|
||||
void rust_stream_write(void *stream, const uint8_t *data, size_t length);
|
||||
// void rust_stream_write(void *stream, const uint8_t *data, size_t length);
|
||||
void rust_stream_seek(void *stream, int64_t offset, int32_t whence);
|
||||
void rust_stream_truncate(void *stream, int64_t length);
|
||||
// void rust_stream_truncate(void *stream, int64_t length);
|
||||
int64_t rust_stream_tell(const void *stream);
|
||||
int64_t rust_stream_length(const void *stream);
|
||||
bool rust_stream_is_readonly(const void *stream);
|
||||
// bool rust_stream_is_readonly(const void *stream);
|
||||
}
|
||||
|
||||
namespace taglib_shim
|
||||
|
@ -47,11 +47,9 @@ namespace taglib_shim
|
|||
return TagLib::ByteVector(reinterpret_cast<char *>(buffer.data()), bytes_read);
|
||||
}
|
||||
|
||||
void RustIOStream::writeBlock(const TagLib::ByteVector &data)
|
||||
void RustIOStream::writeBlock(const TagLib::ByteVector &_)
|
||||
{
|
||||
rust_stream_write(rust_stream,
|
||||
reinterpret_cast<const uint8_t *>(data.data()),
|
||||
data.size());
|
||||
throw std::runtime_error("Stream is read-only");
|
||||
}
|
||||
|
||||
void RustIOStream::insert(const TagLib::ByteVector &data, TagLib::offset_t start, size_t replace)
|
||||
|
@ -127,9 +125,9 @@ namespace taglib_shim
|
|||
seek(0);
|
||||
}
|
||||
|
||||
void RustIOStream::truncate(TagLib::offset_t length)
|
||||
void RustIOStream::truncate(TagLib::offset_t _)
|
||||
{
|
||||
rust_stream_truncate(rust_stream, length);
|
||||
throw std::runtime_error("Stream is read-only");
|
||||
}
|
||||
|
||||
TagLib::offset_t RustIOStream::tell() const
|
||||
|
@ -144,7 +142,7 @@ namespace taglib_shim
|
|||
|
||||
bool RustIOStream::readOnly() const
|
||||
{
|
||||
return rust_stream_is_readonly(rust_stream);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RustIOStream::isOpen() const
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::taglib::TagLibStream;
|
||||
use crate::taglib::{IOStream, Whence};
|
||||
use jni::objects::{JObject, JValue};
|
||||
use jni::JNIEnv;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use alloc::string::String;
|
||||
|
||||
pub struct JInputStream<'local, 'a> {
|
||||
env: &'a mut JNIEnv<'local>,
|
||||
|
@ -20,7 +20,7 @@ impl<'local, 'a> JInputStream<'local, 'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'local, 'a> TagLibStream for JInputStream<'local, 'a> {
|
||||
impl<'local, 'a> IOStream for JInputStream<'local, 'a> {
|
||||
fn name(&mut self) -> String {
|
||||
// Call the Java name() method safely
|
||||
let name = self
|
||||
|
@ -35,18 +35,12 @@ impl<'local, 'a> TagLibStream for JInputStream<'local, 'a> {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn is_readonly(&self) -> bool {
|
||||
true // JInputStream is always read-only
|
||||
}
|
||||
}
|
||||
|
||||
impl<'local, 'a> Read for JInputStream<'local, 'a> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, String> {
|
||||
// Create a direct ByteBuffer from the Rust slice
|
||||
let byte_buffer = unsafe {
|
||||
self.env
|
||||
.new_direct_byte_buffer(buf.as_mut_ptr(), buf.len())
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?
|
||||
.map_err(|_| String::from("Failed to create byte buffer"))?
|
||||
};
|
||||
|
||||
// Call readBlock safely
|
||||
|
@ -59,38 +53,20 @@ impl<'local, 'a> Read for JInputStream<'local, 'a> {
|
|||
&[JValue::Object(&byte_buffer)],
|
||||
)
|
||||
.and_then(|result| result.z())
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
|
||||
.map_err(|_| String::from("Failed to read block"))?;
|
||||
|
||||
if !success {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Failed to read block",
|
||||
));
|
||||
return Err(String::from("Failed to read block"));
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'local, 'a> Write for JInputStream<'local, 'a> {
|
||||
fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::PermissionDenied,
|
||||
"JInputStream is read-only",
|
||||
))
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(()) // Nothing to flush in a read-only stream
|
||||
}
|
||||
}
|
||||
|
||||
impl<'local, 'a> Seek for JInputStream<'local, 'a> {
|
||||
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
|
||||
fn seek(&mut self, pos: Whence) -> Result<(), String> {
|
||||
let (method, offset) = match pos {
|
||||
SeekFrom::Start(offset) => ("seekFromBeginning", offset as i64),
|
||||
SeekFrom::Current(offset) => ("seekFromCurrent", offset),
|
||||
SeekFrom::End(offset) => ("seekFromEnd", offset),
|
||||
Whence::Start(offset) => ("seekFromBeginning", offset as i64),
|
||||
Whence::Current(offset) => ("seekFromCurrent", offset),
|
||||
Whence::End(offset) => ("seekFromEnd", offset),
|
||||
};
|
||||
|
||||
// Call the appropriate seek method safely
|
||||
|
@ -98,29 +74,28 @@ impl<'local, 'a> Seek for JInputStream<'local, 'a> {
|
|||
.env
|
||||
.call_method(&self.input, method, "(J)Z", &[JValue::Long(offset)])
|
||||
.and_then(|result| result.z())
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
|
||||
.map_err(|e| String::from("Failed to seek"))?;
|
||||
|
||||
if !success {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Failed to seek",
|
||||
));
|
||||
return Err(String::from("Failed to seek"));
|
||||
}
|
||||
|
||||
// Return current position safely
|
||||
let position = self
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn tell(&mut self) -> Result<i64, String> {
|
||||
self
|
||||
.env
|
||||
.call_method(&self.input, "tell", "()J", &[])
|
||||
.and_then(|result| result.j())
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
|
||||
.map_err(|_| String::from("Failed to get position"))
|
||||
}
|
||||
|
||||
if position == i64::MIN {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Failed to get position",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(position as u64)
|
||||
fn length(&mut self) -> Result<i64, String> {
|
||||
self
|
||||
.env
|
||||
.call_method(&self.input, "length", "()J", &[])
|
||||
.and_then(|result| result.j())
|
||||
.map_err(|_| String::from("Failed to get length"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
#![no_std]
|
||||
extern crate core;
|
||||
extern crate alloc;
|
||||
|
||||
use jni::objects::{JClass, JObject};
|
||||
use jni::sys::jstring;
|
||||
use jni::JNIEnv;
|
||||
|
@ -8,6 +12,10 @@ mod jni_stream;
|
|||
pub use taglib::*;
|
||||
use jni_stream::JInputStream;
|
||||
|
||||
use alloc::string::String;
|
||||
|
||||
type Map<K, V> = alloc::collections::BTreeMap<K, V>;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn Java_org_oxycblt_musikr_metadata_MetadataJNI_openFile<'local>(
|
||||
mut env: JNIEnv<'local>,
|
||||
|
@ -18,7 +26,7 @@ pub extern "C" fn Java_org_oxycblt_musikr_metadata_MetadataJNI_openFile<'local>(
|
|||
let stream = match JInputStream::new(&mut env, input) {
|
||||
Ok(stream) => stream,
|
||||
Err(e) => {
|
||||
let error = format!("Failed to create input stream: {}", e);
|
||||
let error = String::from("Failed to create input stream");
|
||||
let error_str = env.new_string(error).expect("Couldn't create error string!");
|
||||
return error_str.into_raw();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use std::ffi::CStr;
|
||||
use std::pin::Pin;
|
||||
use std::string::ToString;
|
||||
use std::collections::HashMap;
|
||||
use core::ffi::CStr;
|
||||
use core::pin::Pin;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
#[cxx::bridge]
|
||||
pub(crate) mod bindings {
|
||||
|
@ -340,7 +342,7 @@ impl bindings::XiphComment {
|
|||
}
|
||||
|
||||
impl bindings::SimplePropertyMap {
|
||||
pub fn to_hashmap(&self) -> HashMap<String, Vec<String>> {
|
||||
pub fn to_hashmap(&self) -> BTreeMap<String, Vec<String>> {
|
||||
let cxx_vec = unsafe {
|
||||
// SAFETY:
|
||||
// - This pin is only used in this unsafe scope.
|
||||
|
@ -371,6 +373,7 @@ impl bindings::Property {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for bindings::TString {
|
||||
fn to_string(&self) -> String {
|
||||
let c_str = unsafe {
|
||||
|
|
|
@ -2,52 +2,12 @@ mod ffi;
|
|||
mod stream;
|
||||
|
||||
use ffi::bindings;
|
||||
use std::collections::HashMap;
|
||||
pub use stream::{RustStream, TagLibStream};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::vec::Vec;
|
||||
pub use stream::RustStream;
|
||||
|
||||
type XiphComments = HashMap<String, Vec<String>>;
|
||||
|
||||
pub enum File {
|
||||
Unknown {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
},
|
||||
MP3 {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
},
|
||||
FLAC {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
xiph_comments: Option<XiphComments>,
|
||||
},
|
||||
MP4 {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
},
|
||||
OGG {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
xiph_comments: Option<XiphComments>,
|
||||
},
|
||||
Opus {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
xiph_comments: Option<XiphComments>,
|
||||
},
|
||||
WAV {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
},
|
||||
WavPack {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
},
|
||||
APE {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Audio properties of a media file
|
||||
#[derive(Default)]
|
||||
pub struct AudioProperties {
|
||||
pub length_in_milliseconds: i32,
|
||||
pub bitrate_in_kilobits_per_second: i32,
|
||||
pub sample_rate_in_hz: i32,
|
||||
pub number_of_channels: i32,
|
||||
}
|
||||
|
||||
// Safe wrapper for FileRef that owns extracted data
|
||||
pub struct FileRef {
|
||||
|
@ -56,7 +16,7 @@ pub struct FileRef {
|
|||
|
||||
impl FileRef {
|
||||
/// Create a new FileRef from a stream implementing TagLibStream
|
||||
pub fn from_stream<'a, T: TagLibStream + 'a>(stream: T) -> Option<Self> {
|
||||
pub fn from_stream<'a, T: IOStream + 'a>(stream: T) -> Option<Self> {
|
||||
// Create the RustStream wrapper
|
||||
let rust_stream = stream::RustStream::new(stream);
|
||||
|
||||
|
@ -114,3 +74,64 @@ impl FileRef {
|
|||
&self.file
|
||||
}
|
||||
}
|
||||
|
||||
// Trait that must be implemented by Rust streams to be used with TagLib
|
||||
pub trait IOStream {
|
||||
fn name(&mut self) -> String;
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, String>;
|
||||
fn seek(&mut self, whence: Whence) -> Result<(), String>;
|
||||
fn tell(&mut self) -> Result<i64, String>;
|
||||
fn length(&mut self) -> Result<i64, String>;
|
||||
// No tag editing support, only doing read-only streams for now
|
||||
// fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize>;
|
||||
}
|
||||
|
||||
pub enum Whence {
|
||||
Start(i64),
|
||||
Current(i64),
|
||||
End(i64),
|
||||
}
|
||||
|
||||
/// Audio properties of a media file
|
||||
#[derive(Default)]
|
||||
pub struct AudioProperties {
|
||||
pub length_in_milliseconds: i32,
|
||||
pub bitrate_in_kilobits_per_second: i32,
|
||||
pub sample_rate_in_hz: i32,
|
||||
pub number_of_channels: i32,
|
||||
}
|
||||
|
||||
type XiphComments = BTreeMap<String, Vec<String>>;
|
||||
|
||||
pub enum File {
|
||||
Unknown {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
},
|
||||
MP3 {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
},
|
||||
FLAC {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
xiph_comments: Option<XiphComments>,
|
||||
},
|
||||
MP4 {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
},
|
||||
OGG {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
xiph_comments: Option<XiphComments>,
|
||||
},
|
||||
Opus {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
xiph_comments: Option<XiphComments>,
|
||||
},
|
||||
WAV {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
},
|
||||
WavPack {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
},
|
||||
APE {
|
||||
audio_properties: Option<AudioProperties>,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
use std::ffi::{c_void, CString};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::os::raw::c_char;
|
||||
|
||||
// Trait that must be implemented by Rust streams to be used with TagLib
|
||||
pub trait TagLibStream: Read + Write + Seek {
|
||||
fn name(&mut self) -> String;
|
||||
fn is_readonly(&self) -> bool;
|
||||
}
|
||||
use core::ffi::{c_void, c_char};
|
||||
use alloc::ffi::CString;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::slice;
|
||||
use super::{IOStream, Whence};
|
||||
|
||||
// Opaque type for C++
|
||||
#[repr(C)]
|
||||
pub struct RustStream<'a>(Box<dyn TagLibStream + 'a>);
|
||||
pub struct RustStream<'a>(Box<dyn IOStream + 'a>);
|
||||
|
||||
impl<'a> RustStream<'a> {
|
||||
pub fn new<T: TagLibStream + 'a>(stream: T) -> Self {
|
||||
pub fn new<T: IOStream + 'a>(stream: T) -> Self {
|
||||
RustStream(Box::new(stream))
|
||||
}
|
||||
}
|
||||
|
@ -34,21 +30,10 @@ pub extern "C" fn rust_stream_read(
|
|||
length: usize,
|
||||
) -> usize {
|
||||
let stream = unsafe { &mut *(stream as *mut RustStream<'_>) };
|
||||
let buffer = unsafe { std::slice::from_raw_parts_mut(buffer, length) };
|
||||
let buffer = unsafe { slice::from_raw_parts_mut(buffer, length) };
|
||||
stream.0.read(buffer).unwrap_or(0)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_stream_write(
|
||||
stream: *mut c_void,
|
||||
data: *const u8,
|
||||
length: usize,
|
||||
) {
|
||||
let stream = unsafe { &mut *(stream as *mut RustStream<'_>) };
|
||||
let data = unsafe { std::slice::from_raw_parts(data, length) };
|
||||
stream.0.write_all(data).unwrap();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_stream_seek(
|
||||
stream: *mut c_void,
|
||||
|
@ -57,38 +42,22 @@ pub extern "C" fn rust_stream_seek(
|
|||
) {
|
||||
let stream = unsafe { &mut *(stream as *mut RustStream<'_>) };
|
||||
let pos = match whence {
|
||||
0 => SeekFrom::Start(offset as u64),
|
||||
1 => SeekFrom::Current(offset),
|
||||
2 => SeekFrom::End(offset),
|
||||
0 => Whence::Start(offset),
|
||||
1 => Whence::Current(offset),
|
||||
2 => Whence::End(offset),
|
||||
_ => panic!("Invalid seek whence"),
|
||||
};
|
||||
stream.0.seek(pos).unwrap();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_stream_truncate(stream: *mut c_void, length: i64) {
|
||||
let stream = unsafe { &mut *(stream as *mut RustStream<'_>) };
|
||||
stream.0.seek(SeekFrom::Start(length as u64)).unwrap();
|
||||
// TODO: Actually implement truncate once we have a better trait bound
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_stream_tell(stream: *mut c_void) -> i64 {
|
||||
let stream = unsafe { &mut *(stream as *mut RustStream<'_>) };
|
||||
stream.0.seek(SeekFrom::Current(0)).unwrap() as i64
|
||||
let stream: &mut RustStream<'_> = unsafe { &mut *(stream as *mut RustStream<'_>) };
|
||||
stream.0.tell().unwrap()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_stream_length(stream: *mut c_void) -> i64 {
|
||||
let stream = unsafe { &mut *(stream as *mut RustStream<'_>) };
|
||||
let current = stream.0.seek(SeekFrom::Current(0)).unwrap();
|
||||
let end = stream.0.seek(SeekFrom::End(0)).unwrap();
|
||||
stream.0.seek(SeekFrom::Start(current)).unwrap();
|
||||
end as i64
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_stream_is_readonly(stream: *const c_void) -> bool {
|
||||
let stream = unsafe { &*(stream as *const RustStream<'_>) };
|
||||
stream.0.is_readonly()
|
||||
stream.0.length().unwrap()
|
||||
}
|
Loading…
Reference in a new issue