musikr: reorganize ffi module
Still don't fully like the segregation, but now at least the bridge unsafety is fully isolated.
This commit is contained in:
parent
86b04eaead
commit
3ecdbf289b
12 changed files with 562 additions and 634 deletions
|
@ -47,7 +47,7 @@ fn main() {
|
||||||
} else {
|
} else {
|
||||||
panic!("Unsupported Android target: {}", target);
|
panic!("Unsupported Android target: {}", target);
|
||||||
};
|
};
|
||||||
|
|
||||||
let clang_path = env::var("CLANG_PATH").expect("CLANG_PATH env var not set");
|
let clang_path = env::var("CLANG_PATH").expect("CLANG_PATH env var not set");
|
||||||
let toolchains_marker = "/toolchains";
|
let toolchains_marker = "/toolchains";
|
||||||
let ndk_path = if let Some(pos) = clang_path.find(toolchains_marker) {
|
let ndk_path = if let Some(pos) = clang_path.find(toolchains_marker) {
|
||||||
|
@ -114,7 +114,7 @@ fn main() {
|
||||||
// println!("cargo:rustc-link-lib=cc++_static");
|
// println!("cargo:rustc-link-lib=cc++_static");
|
||||||
|
|
||||||
// Build the shim and cxx bridge together
|
// Build the shim and cxx bridge together
|
||||||
let mut builder = cxx_build::bridge("src/taglib/ffi.rs");
|
let mut builder = cxx_build::bridge("src/taglib/bridge.rs");
|
||||||
builder
|
builder
|
||||||
.file("shim/iostream_shim.cpp")
|
.file("shim/iostream_shim.cpp")
|
||||||
.file("shim/file_shim.cpp")
|
.file("shim/file_shim.cpp")
|
||||||
|
|
|
@ -33,14 +33,14 @@ namespace taglib_shim
|
||||||
return dynamic_cast<TagLib::RIFF::WAV::File *>(file);
|
return dynamic_cast<TagLib::RIFF::WAV::File *>(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
TagLib::WavPack::File *File_asWavPack(TagLib::File *file)
|
// TagLib::WavPack::File *File_asWavPack(TagLib::File *file)
|
||||||
{
|
// {
|
||||||
return dynamic_cast<TagLib::WavPack::File *>(file);
|
// return dynamic_cast<TagLib::WavPack::File *>(file);
|
||||||
}
|
// }
|
||||||
|
|
||||||
TagLib::APE::File *File_asAPE(TagLib::File *file)
|
// TagLib::APE::File *File_asAPE(TagLib::File *file)
|
||||||
{
|
// {
|
||||||
return dynamic_cast<TagLib::APE::File *>(file);
|
// return dynamic_cast<TagLib::APE::File *>(file);
|
||||||
}
|
// }
|
||||||
|
|
||||||
} // namespace taglib_shim
|
} // namespace taglib_shim
|
|
@ -23,7 +23,7 @@ namespace taglib_shim
|
||||||
TagLib::FLAC::File *File_asFLAC(TagLib::File *file);
|
TagLib::FLAC::File *File_asFLAC(TagLib::File *file);
|
||||||
TagLib::MP4::File *File_asMP4(TagLib::File *file);
|
TagLib::MP4::File *File_asMP4(TagLib::File *file);
|
||||||
TagLib::RIFF::WAV::File *File_asWAV(TagLib::File *file);
|
TagLib::RIFF::WAV::File *File_asWAV(TagLib::File *file);
|
||||||
TagLib::WavPack::File *File_asWavPack(TagLib::File *file);
|
// TagLib::WavPack::File *File_asWavPack(TagLib::File *file);
|
||||||
TagLib::APE::File *File_asAPE(TagLib::File *file);
|
// TagLib::APE::File *File_asAPE(TagLib::File *file);
|
||||||
|
|
||||||
} // namespace taglib_shim
|
} // namespace taglib_shim
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::taglib::TagLibStream;
|
use crate::taglib::stream::IOStream;
|
||||||
use jni::objects::{JObject, JValue};
|
use jni::objects::{JObject, JValue};
|
||||||
use jni::JNIEnv;
|
use jni::JNIEnv;
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
|
@ -12,12 +12,12 @@ impl<'local, 'a> JInputStream<'local, 'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
env: &'a mut JNIEnv<'local>,
|
env: &'a mut JNIEnv<'local>,
|
||||||
input: JObject<'local>,
|
input: JObject<'local>,
|
||||||
) -> Result<Self, jni::errors::Error> {
|
) -> Self {
|
||||||
Ok(JInputStream { env, input })
|
Self { env, input }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'local, 'a> TagLibStream for JInputStream<'local, 'a> {
|
impl<'local, 'a> IOStream for JInputStream<'local, 'a> {
|
||||||
fn name(&mut self) -> String {
|
fn name(&mut self) -> String {
|
||||||
// Call the Java name() method safely
|
// Call the Java name() method safely
|
||||||
let name = self
|
let name = self
|
|
@ -2,10 +2,12 @@ use jni::objects::{JClass, JObject};
|
||||||
use jni::sys::jstring;
|
use jni::sys::jstring;
|
||||||
use jni::JNIEnv;
|
use jni::JNIEnv;
|
||||||
|
|
||||||
mod jni_stream;
|
|
||||||
mod taglib;
|
mod taglib;
|
||||||
|
mod jstream;
|
||||||
|
|
||||||
use jni_stream::JInputStream;
|
use taglib::file::FileRef;
|
||||||
|
|
||||||
|
use jstream::JInputStream;
|
||||||
pub use taglib::*;
|
pub use taglib::*;
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
@ -15,29 +17,43 @@ pub extern "C" fn Java_org_oxycblt_musikr_metadata_MetadataJNI_openFile<'local>(
|
||||||
input: JObject<'local>,
|
input: JObject<'local>,
|
||||||
) -> jstring {
|
) -> jstring {
|
||||||
// Create JInputStream from the Java input stream
|
// Create JInputStream from the Java input stream
|
||||||
let stream = match JInputStream::new(&mut env, input) {
|
let stream = JInputStream::new(&mut env, input);
|
||||||
Ok(stream) => stream,
|
let file_ref = FileRef::new(stream);
|
||||||
Err(e) => {
|
// file_ref.file().and_then(|file| {
|
||||||
let error = format!("Failed to create input stream: {}", e);
|
// let audio_properties = file.audio_properties().map(|props| AudioProperties {
|
||||||
let error_str = env
|
// length_in_milliseconds: props.length_in_milliseconds(),
|
||||||
.new_string(error)
|
// bitrate_in_kilobits_per_second: props.bitrate(),
|
||||||
.expect("Couldn't create error string!");
|
// sample_rate_in_hz: props.sample_rate(),
|
||||||
return error_str.into_raw();
|
// number_of_channels: props.channels(),
|
||||||
}
|
// });
|
||||||
};
|
|
||||||
|
|
||||||
// Create FileRef from the stream
|
// if let Some(vorbis_file) = file.as_vorbis() {
|
||||||
let file_ref = match FileRef::from_stream(stream) {
|
// let xiph_comments = vorbis_file
|
||||||
Some(file_ref) => file_ref,
|
// .xiph_comments()
|
||||||
None => {
|
// .map(|comments| comments.field_list_map().to_hashmap());
|
||||||
let error = "Failed to create File";
|
|
||||||
let error_str = env
|
|
||||||
.new_string(error)
|
|
||||||
.expect("Couldn't create error string!");
|
|
||||||
return error_str.into_raw();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// } else if let Some(opus_file) = file.as_opus() {
|
||||||
|
// let xiph_comments = opus_file
|
||||||
|
// .xiph_comments()
|
||||||
|
// .map(|comments| comments.field_list_map().to_hashmap());
|
||||||
|
|
||||||
|
// Some(File::Opus {
|
||||||
|
// audio_properties,
|
||||||
|
// xiph_comments,
|
||||||
|
// })
|
||||||
|
// } else if let Some(flac_file) = file.as_flac() {
|
||||||
|
// let xiph_comments = flac_file
|
||||||
|
// .xiph_comments()
|
||||||
|
// .map(|comments| comments.field_list_map().to_hashmap());
|
||||||
|
// Some(File::FLAC {
|
||||||
|
// audio_properties,
|
||||||
|
// xiph_comments,
|
||||||
|
// })
|
||||||
|
// } else {
|
||||||
|
// Some(File::Unknown { audio_properties })
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
// Return the title
|
// Return the title
|
||||||
let output = env.new_string("title").expect("Couldn't create string!");
|
let output = env.new_string("title").expect("Couldn't create string!");
|
||||||
output.into_raw()
|
output.into_raw()
|
||||||
|
|
141
musikr/src/main/jni/src/taglib/bridge.rs
Normal file
141
musikr/src/main/jni/src/taglib/bridge.rs
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
#[cxx::bridge]
|
||||||
|
mod bridge_impl {
|
||||||
|
unsafe extern "C++" {
|
||||||
|
include!("taglib/taglib.h");
|
||||||
|
include!("taglib/tstring.h");
|
||||||
|
include!("taglib/tstringlist.h");
|
||||||
|
include!("taglib/vorbisfile.h");
|
||||||
|
include!("taglib/xiphcomment.h");
|
||||||
|
include!("shim/iostream_shim.hpp");
|
||||||
|
include!("shim/file_shim.hpp");
|
||||||
|
include!("shim/tk_shim.hpp");
|
||||||
|
|
||||||
|
#[namespace = "TagLib"]
|
||||||
|
#[cxx_name = "FileRef"]
|
||||||
|
type TFileRef;
|
||||||
|
#[cxx_name = "isNull"]
|
||||||
|
fn thisIsNull(self: Pin<&TFileRef>) -> bool;
|
||||||
|
#[cxx_name = "file"]
|
||||||
|
fn thisFile(self: Pin<&TFileRef>) -> *mut BaseFile;
|
||||||
|
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
|
type RustIOStream;
|
||||||
|
// Create a FileRef from an iostream
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
|
unsafe fn new_rust_iostream(stream: *mut RustStream) -> UniquePtr<RustIOStream>;
|
||||||
|
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
|
type RustStream;
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
|
fn new_FileRef_from_stream(stream: UniquePtr<RustIOStream>) -> UniquePtr<TFileRef>;
|
||||||
|
|
||||||
|
#[namespace = "TagLib"]
|
||||||
|
#[cxx_name = "File"]
|
||||||
|
type BaseFile;
|
||||||
|
#[cxx_name = "audioProperties"]
|
||||||
|
fn thisAudioProperties(self: Pin<&BaseFile>) -> *mut AudioProperties;
|
||||||
|
|
||||||
|
#[namespace = "TagLib"]
|
||||||
|
type AudioProperties;
|
||||||
|
#[cxx_name = "lengthInMilliseconds"]
|
||||||
|
fn thisLengthInMilliseconds(self: Pin<&AudioProperties>) -> i32;
|
||||||
|
#[cxx_name = "bitrate"]
|
||||||
|
fn thisBitrate(self: Pin<&AudioProperties>) -> i32;
|
||||||
|
#[cxx_name = "sampleRate"]
|
||||||
|
fn thisSampleRate(self: Pin<&AudioProperties>) -> i32;
|
||||||
|
#[cxx_name = "channels"]
|
||||||
|
fn thisChannels(self: Pin<&AudioProperties>) -> i32;
|
||||||
|
|
||||||
|
#[namespace = "TagLib::Ogg"]
|
||||||
|
#[cxx_name = "File"]
|
||||||
|
type OggFile;
|
||||||
|
|
||||||
|
#[namespace = "TagLib::Ogg"]
|
||||||
|
type XiphComment;
|
||||||
|
#[cxx_name = "fieldListMap"]
|
||||||
|
unsafe fn thisFieldListMap(self: Pin<&XiphComment>) -> &SimplePropertyMap;
|
||||||
|
|
||||||
|
#[namespace = "TagLib::Ogg::Vorbis"]
|
||||||
|
#[cxx_name = "File"]
|
||||||
|
type VorbisFile;
|
||||||
|
#[cxx_name = "tag"]
|
||||||
|
unsafe fn vorbisThisTag(self: Pin<&VorbisFile>) -> *mut XiphComment;
|
||||||
|
|
||||||
|
#[namespace = "TagLib::Ogg::Opus"]
|
||||||
|
#[cxx_name = "File"]
|
||||||
|
type OpusFile;
|
||||||
|
#[cxx_name = "tag"]
|
||||||
|
unsafe fn opusThisTag(self: Pin<&OpusFile>) -> *mut XiphComment;
|
||||||
|
|
||||||
|
#[namespace = "TagLib::FLAC"]
|
||||||
|
#[cxx_name = "File"]
|
||||||
|
type FLACFile;
|
||||||
|
#[cxx_name = "xiphComment"]
|
||||||
|
unsafe fn flacThisXiphComment(self: Pin<&mut FLACFile>, create: bool) -> *mut XiphComment;
|
||||||
|
|
||||||
|
#[namespace = "TagLib::MPEG"]
|
||||||
|
#[cxx_name = "File"]
|
||||||
|
type MPEGFile;
|
||||||
|
|
||||||
|
#[namespace = "TagLib::MP4"]
|
||||||
|
#[cxx_name = "File"]
|
||||||
|
type MP4File;
|
||||||
|
|
||||||
|
#[namespace = "TagLib::RIFF::WAV"]
|
||||||
|
#[cxx_name = "File"]
|
||||||
|
type WAVFile;
|
||||||
|
|
||||||
|
// #[namespace = "TagLib::WavPack"]
|
||||||
|
// #[cxx_name = "File"]
|
||||||
|
// type WavPackFile;
|
||||||
|
|
||||||
|
// #[namespace = "TagLib::APE"]
|
||||||
|
// #[cxx_name = "File"]
|
||||||
|
// type APEFile;
|
||||||
|
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
|
unsafe fn File_asVorbis(file: *mut BaseFile) -> *mut VorbisFile;
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
|
unsafe fn File_asOpus(file: *mut BaseFile) -> *mut OpusFile;
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
|
unsafe fn File_asMPEG(file: *mut BaseFile) -> *mut MPEGFile;
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
|
unsafe fn File_asFLAC(file: *mut BaseFile) -> *mut FLACFile;
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
|
unsafe fn File_asMP4(file: *mut BaseFile) -> *mut MP4File;
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
|
unsafe fn File_asWAV(file: *mut BaseFile) -> *mut WAVFile;
|
||||||
|
// #[namespace = "taglib_shim"]
|
||||||
|
// unsafe fn File_asWavPack(file: *mut BaseFile) -> *mut WavPackFile;
|
||||||
|
// #[namespace = "taglib_shim"]
|
||||||
|
// unsafe fn File_asAPE(file: *mut BaseFile) -> *mut APEFile;
|
||||||
|
|
||||||
|
#[namespace = "TagLib"]
|
||||||
|
type SimplePropertyMap;
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
|
fn SimplePropertyMap_to_vector(
|
||||||
|
field_list_map: Pin<&SimplePropertyMap>,
|
||||||
|
) -> UniquePtr<CxxVector<Property>>;
|
||||||
|
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
|
type Property;
|
||||||
|
#[cxx_name = "key"]
|
||||||
|
fn thisKey(self: Pin<&Property>) -> &TString;
|
||||||
|
#[cxx_name = "value"]
|
||||||
|
unsafe fn thisValue(self: Pin<&Property>) -> &TStringList;
|
||||||
|
|
||||||
|
#[namespace = "TagLib"]
|
||||||
|
#[cxx_name = "String"]
|
||||||
|
type TString;
|
||||||
|
#[cxx_name = "toCString"]
|
||||||
|
unsafe fn thisToCString(self: Pin<&TString>, unicode: bool) -> *const c_char;
|
||||||
|
|
||||||
|
#[namespace = "TagLib"]
|
||||||
|
#[cxx_name = "StringList"]
|
||||||
|
type TStringList;
|
||||||
|
#[namespace = "taglib_shim"]
|
||||||
|
fn StringList_to_vector(string_list: Pin<&TStringList>) -> UniquePtr<CxxVector<TString>>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use bridge_impl::*;
|
|
@ -1,457 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::pin::{pin, Pin};
|
|
||||||
use std::string::ToString;
|
|
||||||
|
|
||||||
#[cxx::bridge]
|
|
||||||
pub(crate) mod bindings {
|
|
||||||
unsafe extern "C++" {
|
|
||||||
include!("taglib/taglib.h");
|
|
||||||
include!("taglib/tstring.h");
|
|
||||||
include!("taglib/tstringlist.h");
|
|
||||||
include!("taglib/vorbisfile.h");
|
|
||||||
include!("taglib/xiphcomment.h");
|
|
||||||
include!("shim/iostream_shim.hpp");
|
|
||||||
include!("shim/file_shim.hpp");
|
|
||||||
include!("shim/tk_shim.hpp");
|
|
||||||
|
|
||||||
#[namespace = "TagLib"]
|
|
||||||
type FileRef;
|
|
||||||
#[cxx_name = "isNull"]
|
|
||||||
fn thisIsNull(self: Pin<&FileRef>) -> bool;
|
|
||||||
#[cxx_name = "file"]
|
|
||||||
fn thisFile(self: Pin<&FileRef>) -> *mut BaseFile;
|
|
||||||
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
type RustIOStream;
|
|
||||||
// Create a FileRef from an iostream
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
unsafe fn new_rust_iostream(stream: *mut RustStream) -> UniquePtr<RustIOStream>;
|
|
||||||
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
type RustStream;
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
fn new_FileRef_from_stream(stream: UniquePtr<RustIOStream>) -> UniquePtr<FileRef>;
|
|
||||||
|
|
||||||
#[namespace = "TagLib"]
|
|
||||||
#[cxx_name = "File"]
|
|
||||||
type BaseFile;
|
|
||||||
#[cxx_name = "audioProperties"]
|
|
||||||
fn thisAudioProperties(self: Pin<&BaseFile>) -> *mut AudioProperties;
|
|
||||||
|
|
||||||
#[namespace = "TagLib"]
|
|
||||||
type AudioProperties;
|
|
||||||
#[cxx_name = "lengthInMilliseconds"]
|
|
||||||
fn thisLengthInMilliseconds(self: Pin<&AudioProperties>) -> i32;
|
|
||||||
#[cxx_name = "bitrate"]
|
|
||||||
fn thisBitrate(self: Pin<&AudioProperties>) -> i32;
|
|
||||||
#[cxx_name = "sampleRate"]
|
|
||||||
fn thisSampleRate(self: Pin<&AudioProperties>) -> i32;
|
|
||||||
#[cxx_name = "channels"]
|
|
||||||
fn thisChannels(self: Pin<&AudioProperties>) -> i32;
|
|
||||||
|
|
||||||
#[namespace = "TagLib::Ogg"]
|
|
||||||
#[cxx_name = "File"]
|
|
||||||
type OggFile;
|
|
||||||
|
|
||||||
#[namespace = "TagLib::Ogg"]
|
|
||||||
type XiphComment;
|
|
||||||
#[cxx_name = "fieldListMap"]
|
|
||||||
unsafe fn thisFieldListMap(self: Pin<&XiphComment>) -> &SimplePropertyMap;
|
|
||||||
|
|
||||||
#[namespace = "TagLib::Ogg::Vorbis"]
|
|
||||||
#[cxx_name = "File"]
|
|
||||||
type VorbisFile;
|
|
||||||
#[cxx_name = "tag"]
|
|
||||||
unsafe fn vorbisThisTag(self: Pin<&VorbisFile>) -> *mut XiphComment;
|
|
||||||
|
|
||||||
#[namespace = "TagLib::Ogg::Opus"]
|
|
||||||
#[cxx_name = "File"]
|
|
||||||
type OpusFile;
|
|
||||||
#[cxx_name = "tag"]
|
|
||||||
unsafe fn opusThisTag(self: Pin<&OpusFile>) -> *mut XiphComment;
|
|
||||||
|
|
||||||
#[namespace = "TagLib::FLAC"]
|
|
||||||
#[cxx_name = "File"]
|
|
||||||
type FLACFile;
|
|
||||||
#[cxx_name = "xiphComment"]
|
|
||||||
unsafe fn flacThisXiphComment(self: Pin<&mut FLACFile>, create: bool) -> *mut XiphComment;
|
|
||||||
|
|
||||||
#[namespace = "TagLib::MPEG"]
|
|
||||||
#[cxx_name = "File"]
|
|
||||||
type MPEGFile;
|
|
||||||
|
|
||||||
#[namespace = "TagLib::MP4"]
|
|
||||||
#[cxx_name = "File"]
|
|
||||||
type MP4File;
|
|
||||||
|
|
||||||
#[namespace = "TagLib::RIFF::WAV"]
|
|
||||||
#[cxx_name = "File"]
|
|
||||||
type WAVFile;
|
|
||||||
|
|
||||||
#[namespace = "TagLib::WavPack"]
|
|
||||||
#[cxx_name = "File"]
|
|
||||||
type WavPackFile;
|
|
||||||
|
|
||||||
#[namespace = "TagLib::APE"]
|
|
||||||
#[cxx_name = "File"]
|
|
||||||
type APEFile;
|
|
||||||
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
unsafe fn File_asVorbis(file: *mut BaseFile) -> *mut VorbisFile;
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
unsafe fn File_asOpus(file: *mut BaseFile) -> *mut OpusFile;
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
unsafe fn File_asMPEG(file: *mut BaseFile) -> *mut MPEGFile;
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
unsafe fn File_asFLAC(file: *mut BaseFile) -> *mut FLACFile;
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
unsafe fn File_asMP4(file: *mut BaseFile) -> *mut MP4File;
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
unsafe fn File_asWAV(file: *mut BaseFile) -> *mut WAVFile;
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
unsafe fn File_asWavPack(file: *mut BaseFile) -> *mut WavPackFile;
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
unsafe fn File_asAPE(file: *mut BaseFile) -> *mut APEFile;
|
|
||||||
|
|
||||||
#[namespace = "TagLib"]
|
|
||||||
type SimplePropertyMap;
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
fn SimplePropertyMap_to_vector(
|
|
||||||
field_list_map: Pin<&SimplePropertyMap>,
|
|
||||||
) -> UniquePtr<CxxVector<Property>>;
|
|
||||||
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
type Property;
|
|
||||||
#[cxx_name = "key"]
|
|
||||||
fn thisKey(self: Pin<&Property>) -> &TString;
|
|
||||||
#[cxx_name = "value"]
|
|
||||||
unsafe fn thisValue(self: Pin<&Property>) -> &TStringList;
|
|
||||||
|
|
||||||
#[namespace = "TagLib"]
|
|
||||||
#[cxx_name = "String"]
|
|
||||||
type TString;
|
|
||||||
#[cxx_name = "toCString"]
|
|
||||||
unsafe fn thisToCString(self: Pin<&TString>, unicode: bool) -> *const c_char;
|
|
||||||
|
|
||||||
#[namespace = "TagLib"]
|
|
||||||
#[cxx_name = "StringList"]
|
|
||||||
type TStringList;
|
|
||||||
#[namespace = "taglib_shim"]
|
|
||||||
fn StringList_to_vector(string_list: Pin<&TStringList>) -> UniquePtr<CxxVector<TString>>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl bindings::FileRef {
|
|
||||||
pub fn file_or(&self) -> Option<&mut bindings::BaseFile> {
|
|
||||||
let file = unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The file data is a pointer that does not depend on the
|
|
||||||
// address of self.
|
|
||||||
let this = Pin::new_unchecked(&*self);
|
|
||||||
// Note: This is not the rust ptr "is_null", but a taglib isNull method
|
|
||||||
// that checks for file validity. Without this check, we can get corrupted
|
|
||||||
// file ptrs.
|
|
||||||
if !this.thisIsNull() {
|
|
||||||
Some(this.thisFile())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
file.and_then(|file| unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
|
||||||
// - The null-safe version is being used.
|
|
||||||
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
|
||||||
// - There are no datapaths that will yield any mutable pointers or references
|
|
||||||
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
|
||||||
file.as_mut()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl bindings::BaseFile {
|
|
||||||
pub fn audio_properties(&self) -> Option<&bindings::AudioProperties> {
|
|
||||||
let props = unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The audio properties data is a pointer that does not depend on the
|
|
||||||
// address of self.
|
|
||||||
let this: Pin<&bindings::BaseFile> = Pin::new_unchecked(self);
|
|
||||||
this.thisAudioProperties()
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
|
||||||
// - The null-safe version is being used.
|
|
||||||
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
|
||||||
// - There are no datapaths that will yield any mutable pointers or references
|
|
||||||
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
|
||||||
props.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_opus(&mut self) -> Option<&mut bindings::OpusFile> {
|
|
||||||
let ptr_self = self as *mut Self;
|
|
||||||
let opus_file = unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// This FFI function will be a simple C++ dynamic_cast, which checks if
|
|
||||||
// the file can be cased down to an opus file. If the cast fails, a null
|
|
||||||
// pointer is returned, which will be handled by as_ref's null checking.
|
|
||||||
bindings::File_asOpus(ptr_self)
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
|
||||||
// - The null-safe version is being used.
|
|
||||||
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
|
||||||
// - There are no datapaths that will yield any mutable pointers or references
|
|
||||||
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
|
||||||
opus_file.as_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_vorbis(&mut self) -> Option<&bindings::VorbisFile> {
|
|
||||||
let ptr_self = self as *mut Self;
|
|
||||||
let vorbis_file = unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// This FFI function will be a simple C++ dynamic_cast, which checks if
|
|
||||||
// the file can be cased down to an opus file. If the cast fails, a null
|
|
||||||
// pointer is returned, which will be handled by as_ref's null checking.
|
|
||||||
bindings::File_asVorbis(ptr_self)
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
|
||||||
// - The null-safe version is being used.
|
|
||||||
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
|
||||||
// - There are no datapaths that will yield any mutable pointers or references
|
|
||||||
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
|
||||||
vorbis_file.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_flac(&mut self) -> Option<&mut bindings::FLACFile> {
|
|
||||||
let ptr_self = self as *mut Self;
|
|
||||||
let flac_file = unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// This FFI function will be a simple C++ dynamic_cast, which checks if
|
|
||||||
// the file can be cased down to an opus file. If the cast fails, a null
|
|
||||||
// pointer is returned, which will be handled by as_ref's null checking.
|
|
||||||
bindings::File_asFLAC(ptr_self)
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
|
||||||
// - The null-safe version is being used.
|
|
||||||
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
|
||||||
// - There are no datapaths that will yield any mutable pointers or references
|
|
||||||
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
|
||||||
flac_file.as_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl bindings::AudioProperties {
|
|
||||||
pub fn length_in_milliseconds(&self) -> i32 {
|
|
||||||
unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The value is copied and thus not dependent on the address of self.
|
|
||||||
let this = Pin::new_unchecked(self);
|
|
||||||
this.thisLengthInMilliseconds()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bitrate(&self) -> i32 {
|
|
||||||
unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The value is copied and thus not dependent on the address of self.
|
|
||||||
let this = Pin::new_unchecked(self);
|
|
||||||
this.thisBitrate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sample_rate(&self) -> i32 {
|
|
||||||
unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The value is copied and thus not dependent on the address of self.
|
|
||||||
let this = Pin::new_unchecked(self);
|
|
||||||
this.thisSampleRate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn channels(&self) -> i32 {
|
|
||||||
unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The value is copied and thus not dependent on the address of self.
|
|
||||||
let this = Pin::new_unchecked(self);
|
|
||||||
this.thisChannels()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl bindings::OpusFile {
|
|
||||||
pub fn xiph_comments(&self) -> Option<&bindings::XiphComment> {
|
|
||||||
let tag = unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The value is a pointer that does not depend on the address of self.
|
|
||||||
let this = Pin::new_unchecked(self);
|
|
||||||
this.opusThisTag()
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
// SAFETY: This pointer is a valid type, and can only used and accessed
|
|
||||||
// via this function and thus cannot be mutated, satisfying the aliasing rules.
|
|
||||||
tag.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl bindings::VorbisFile {
|
|
||||||
pub fn xiph_comments(&self) -> Option<&bindings::XiphComment> {
|
|
||||||
let tag = unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The value is a pointer that does not depend on the address of self.
|
|
||||||
let this = Pin::new_unchecked(self);
|
|
||||||
this.vorbisThisTag()
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
// SAFETY: This pointer is a valid type, and can only used and accessed
|
|
||||||
// via this function and thus cannot be mutated, satisfying the aliasing rules.
|
|
||||||
tag.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl bindings::FLACFile {
|
|
||||||
pub fn xiph_comments(&mut self) -> Option<&bindings::XiphComment> {
|
|
||||||
let tag = unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The value is a pointer that does not depend on the address of self.
|
|
||||||
let this = Pin::new_unchecked(self);
|
|
||||||
// SAFETY: This is a C++ FFI function ensured to call correctly.
|
|
||||||
this.flacThisXiphComment(false)
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
// SAFETY: This pointer is a valid type, and can only used and accessed
|
|
||||||
// via this function and thus cannot be mutated, satisfying the aliasing rules.
|
|
||||||
tag.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl bindings::XiphComment {
|
|
||||||
pub fn field_list_map(&self) -> &bindings::SimplePropertyMap {
|
|
||||||
unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The value is a reference that does not depend on the address of self.
|
|
||||||
let this = Pin::new_unchecked(self);
|
|
||||||
this.thisFieldListMap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl bindings::SimplePropertyMap {
|
|
||||||
pub fn to_hashmap(&self) -> HashMap<String, Vec<String>> {
|
|
||||||
let cxx_vec = unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The value is a unique_ptr to a copied vector that is not dependent
|
|
||||||
// on the address of self.
|
|
||||||
let this = Pin::new_unchecked(self);
|
|
||||||
bindings::SimplePropertyMap_to_vector(this)
|
|
||||||
};
|
|
||||||
cxx_vec.iter().map(|property| property.to_tuple()).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl bindings::Property {
|
|
||||||
pub fn to_tuple(&self) -> (String, Vec<String>) {
|
|
||||||
unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The values returned are copied and thus not dependent on the address
|
|
||||||
// of self.
|
|
||||||
let this = Pin::new_unchecked(self);
|
|
||||||
let key = this.thisKey().to_string();
|
|
||||||
let value = this.thisValue().to_vec();
|
|
||||||
(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl ToString for bindings::TString {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
let c_str = unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The value returned are pointers and thus not dependent on the address
|
|
||||||
// of self.
|
|
||||||
let this: Pin<&bindings::TString> = Pin::new_unchecked(self);
|
|
||||||
this.thisToCString(true)
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This is a C-string returned by a C++ method guaranteed to have
|
|
||||||
// a null terminator.
|
|
||||||
// - This C-string is fully allocated and owned by the TagString instance,
|
|
||||||
// in a continous block from start to null terminator.
|
|
||||||
// - This C-string will be non-null even if empty.
|
|
||||||
// - This pointer will not be mutated before it's entirely copied into
|
|
||||||
// rust.
|
|
||||||
// - This C-string is copied to a rust string before TagString is destroyed.
|
|
||||||
CStr::from_ptr(c_str)
|
|
||||||
}
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl bindings::TStringList {
|
|
||||||
pub fn to_vec(&self) -> Vec<String> {
|
|
||||||
let cxx_values = unsafe {
|
|
||||||
// SAFETY:
|
|
||||||
// - This pin is only used in this unsafe scope.
|
|
||||||
// - The pin is used as a C++ this pointer in the ffi call, which does
|
|
||||||
// not change address by C++ semantics.
|
|
||||||
// - The value returned is a unique ptr to a copied vector that is not
|
|
||||||
// dependent on the address of self.
|
|
||||||
let this = Pin::new_unchecked(self);
|
|
||||||
bindings::StringList_to_vector(this)
|
|
||||||
};
|
|
||||||
cxx_values.iter().map(|value| value.to_string()).collect()
|
|
||||||
}
|
|
||||||
}
|
|
189
musikr/src/main/jni/src/taglib/file.rs
Normal file
189
musikr/src/main/jni/src/taglib/file.rs
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
use cxx::UniquePtr;
|
||||||
|
use super::bridge::{self, TFileRef};
|
||||||
|
pub use super::bridge::{BaseFile as File, AudioProperties};
|
||||||
|
use super::xiph::{OpusFile, VorbisFile, FLACFile};
|
||||||
|
use super::stream::BridgeStream;
|
||||||
|
use super::stream::IOStream;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
pub struct FileRef<'a, T: IOStream + 'a> {
|
||||||
|
data: PhantomData<&'a T>,
|
||||||
|
ptr: UniquePtr<TFileRef>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <'a, T: IOStream + 'a> FileRef<'a, T> {
|
||||||
|
pub fn new(stream: T) -> FileRef<'a, T> {
|
||||||
|
let bridge_stream = BridgeStream::new(stream);
|
||||||
|
let raw_stream = Box::into_raw(Box::new(bridge_stream)) as *mut bridge::RustStream;
|
||||||
|
let iostream = unsafe { bridge::new_rust_iostream(raw_stream) };
|
||||||
|
let file_ref = bridge::new_FileRef_from_stream(iostream);
|
||||||
|
FileRef {
|
||||||
|
data: PhantomData::<&'a T>,
|
||||||
|
ptr: file_ref
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file(&self) -> Option<&mut File> {
|
||||||
|
let file = unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The file data is a pointer that does not depend on the
|
||||||
|
// address of self.
|
||||||
|
let this = Pin::new_unchecked(&*self.ptr);
|
||||||
|
// Note: This is not the rust ptr "is_null", but a taglib isNull method
|
||||||
|
// that checks for file validity. Without this check, we can get corrupted
|
||||||
|
// file ptrs.
|
||||||
|
if !this.thisIsNull() {
|
||||||
|
Some(this.thisFile())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
file.and_then(|file| unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
||||||
|
// - The null-safe version is being used.
|
||||||
|
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
||||||
|
// - There are no datapaths that will yield any mutable pointers or references
|
||||||
|
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
||||||
|
file.as_mut()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl File {
|
||||||
|
pub fn audio_properties(&self) -> Option<&AudioProperties> {
|
||||||
|
let props = unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The audio properties data is a pointer that does not depend on the
|
||||||
|
// address of self.
|
||||||
|
let this: Pin<&File> = Pin::new_unchecked(self);
|
||||||
|
this.thisAudioProperties()
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
||||||
|
// - The null-safe version is being used.
|
||||||
|
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
||||||
|
// - There are no datapaths that will yield any mutable pointers or references
|
||||||
|
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
||||||
|
props.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_opus(&mut self) -> Option<&mut OpusFile> {
|
||||||
|
let ptr_self = self as *mut Self;
|
||||||
|
let opus_file = unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// This FFI function will be a simple C++ dynamic_cast, which checks if
|
||||||
|
// the file can be cased down to an opus file. If the cast fails, a null
|
||||||
|
// pointer is returned, which will be handled by as_ref's null checking.
|
||||||
|
bridge::File_asOpus(ptr_self)
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
||||||
|
// - The null-safe version is being used.
|
||||||
|
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
||||||
|
// - There are no datapaths that will yield any mutable pointers or references
|
||||||
|
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
||||||
|
opus_file.as_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_vorbis(&mut self) -> Option<&VorbisFile> {
|
||||||
|
let ptr_self = self as *mut Self;
|
||||||
|
let vorbis_file = unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// This FFI function will be a simple C++ dynamic_cast, which checks if
|
||||||
|
// the file can be cased down to an opus file. If the cast fails, a null
|
||||||
|
// pointer is returned, which will be handled by as_ref's null checking.
|
||||||
|
bridge::File_asVorbis(ptr_self)
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
||||||
|
// - The null-safe version is being used.
|
||||||
|
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
||||||
|
// - There are no datapaths that will yield any mutable pointers or references
|
||||||
|
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
||||||
|
vorbis_file.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_flac(&mut self) -> Option<&mut FLACFile> {
|
||||||
|
let ptr_self = self as *mut Self;
|
||||||
|
let flac_file = unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// This FFI function will be a simple C++ dynamic_cast, which checks if
|
||||||
|
// the file can be cased down to an opus file. If the cast fails, a null
|
||||||
|
// pointer is returned, which will be handled by as_ref's null checking.
|
||||||
|
bridge::File_asFLAC(ptr_self)
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This points to a C++ FFI type ensured to be aligned by cxx's codegen.
|
||||||
|
// - The null-safe version is being used.
|
||||||
|
// - This points to a C++FFI type ensured to be valid by cxx's codegen.
|
||||||
|
// - There are no datapaths that will yield any mutable pointers or references
|
||||||
|
// to this, ensuring that it will not be mutated as per the aliasing rules.
|
||||||
|
flac_file.as_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioProperties {
|
||||||
|
pub fn length_in_milliseconds(&self) -> i32 {
|
||||||
|
unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The value is copied and thus not dependent on the address of self.
|
||||||
|
let this = Pin::new_unchecked(self);
|
||||||
|
this.thisLengthInMilliseconds()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bitrate(&self) -> i32 {
|
||||||
|
unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The value is copied and thus not dependent on the address of self.
|
||||||
|
let this = Pin::new_unchecked(self);
|
||||||
|
this.thisBitrate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample_rate(&self) -> i32 {
|
||||||
|
unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The value is copied and thus not dependent on the address of self.
|
||||||
|
let this = Pin::new_unchecked(self);
|
||||||
|
this.thisSampleRate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn channels(&self) -> i32 {
|
||||||
|
unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The value is copied and thus not dependent on the address of self.
|
||||||
|
let this = Pin::new_unchecked(self);
|
||||||
|
this.thisChannels()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,124 +1,6 @@
|
||||||
mod ffi;
|
mod bridge;
|
||||||
mod stream;
|
|
||||||
|
|
||||||
use ffi::bindings;
|
pub mod file;
|
||||||
use std::collections::HashMap;
|
pub mod stream;
|
||||||
pub use stream::{RustStream, TagLibStream};
|
pub mod tk;
|
||||||
|
pub mod xiph;
|
||||||
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 {
|
|
||||||
file: Option<File>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileRef {
|
|
||||||
/// Create a new FileRef from a stream implementing TagLibStream
|
|
||||||
pub fn from_stream<'a, T: TagLibStream + 'a>(stream: T) -> Option<Self> {
|
|
||||||
// Create the RustStream wrapper
|
|
||||||
let rust_stream = stream::RustStream::new(stream);
|
|
||||||
|
|
||||||
// Convert to raw pointer for FFI
|
|
||||||
let raw_stream = Box::into_raw(Box::new(rust_stream)) as *mut bindings::RustStream;
|
|
||||||
|
|
||||||
// Create the RustIOStream C++ wrapper
|
|
||||||
let iostream = unsafe { ffi::bindings::new_rust_iostream(raw_stream) };
|
|
||||||
|
|
||||||
// Create FileRef from iostream
|
|
||||||
let file_ref = ffi::bindings::new_FileRef_from_stream(iostream);
|
|
||||||
if file_ref.is_null() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract data from C++ objects
|
|
||||||
let file = file_ref.file_or().and_then(|file| {
|
|
||||||
let audio_properties = file.audio_properties().map(|props| AudioProperties {
|
|
||||||
length_in_milliseconds: props.length_in_milliseconds(),
|
|
||||||
bitrate_in_kilobits_per_second: props.bitrate(),
|
|
||||||
sample_rate_in_hz: props.sample_rate(),
|
|
||||||
number_of_channels: props.channels(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(vorbis_file) = file.as_vorbis() {
|
|
||||||
let xiph_comments = vorbis_file
|
|
||||||
.xiph_comments()
|
|
||||||
.map(|comments| comments.field_list_map().to_hashmap());
|
|
||||||
|
|
||||||
Some(File::OGG {
|
|
||||||
audio_properties,
|
|
||||||
xiph_comments,
|
|
||||||
})
|
|
||||||
} else if let Some(opus_file) = file.as_opus() {
|
|
||||||
let xiph_comments = opus_file
|
|
||||||
.xiph_comments()
|
|
||||||
.map(|comments| comments.field_list_map().to_hashmap());
|
|
||||||
|
|
||||||
Some(File::Opus {
|
|
||||||
audio_properties,
|
|
||||||
xiph_comments,
|
|
||||||
})
|
|
||||||
} else if let Some(flac_file) = file.as_flac() {
|
|
||||||
let xiph_comments = flac_file
|
|
||||||
.xiph_comments()
|
|
||||||
.map(|comments| comments.field_list_map().to_hashmap());
|
|
||||||
Some(File::FLAC {
|
|
||||||
audio_properties,
|
|
||||||
xiph_comments,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Some(File::Unknown { audio_properties })
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clean up C++ objects - they will be dropped when file_ref is dropped
|
|
||||||
drop(file_ref);
|
|
||||||
|
|
||||||
Some(FileRef { file })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file(&self) -> &Option<File> {
|
|
||||||
&self.file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,25 +2,23 @@ use std::ffi::{c_void, CString};
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
use std::os::raw::c_char;
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
// Trait that must be implemented by Rust streams to be used with TagLib
|
pub trait IOStream: Read + Write + Seek {
|
||||||
pub trait TagLibStream: Read + Write + Seek {
|
|
||||||
fn name(&mut self) -> String;
|
fn name(&mut self) -> String;
|
||||||
fn is_readonly(&self) -> bool;
|
fn is_readonly(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opaque type for C++
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct RustStream<'a>(Box<dyn TagLibStream + 'a>);
|
pub(super) struct BridgeStream<'a>(Box<dyn IOStream + 'a>);
|
||||||
|
|
||||||
impl<'a> RustStream<'a> {
|
impl<'a> BridgeStream<'a> {
|
||||||
pub fn new<T: TagLibStream + 'a>(stream: T) -> Self {
|
pub fn new<T: IOStream + 'a>(stream: T) -> Self {
|
||||||
RustStream(Box::new(stream))
|
BridgeStream(Box::new(stream))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_stream_name(stream: *mut c_void) -> *const c_char {
|
pub extern "C" fn rust_stream_name(stream: *mut c_void) -> *const c_char {
|
||||||
let stream = unsafe { &mut *(stream as *mut RustStream<'_>) };
|
let stream = unsafe { &mut *(stream as *mut BridgeStream<'_>) };
|
||||||
let name = stream.0.name();
|
let name = stream.0.name();
|
||||||
// Note: This leaks memory, but TagLib only calls this once during construction
|
// Note: This leaks memory, but TagLib only calls this once during construction
|
||||||
// and keeps the pointer, so it's fine
|
// and keeps the pointer, so it's fine
|
||||||
|
@ -29,21 +27,21 @@ pub extern "C" fn rust_stream_name(stream: *mut c_void) -> *const c_char {
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_stream_read(stream: *mut c_void, buffer: *mut u8, length: usize) -> usize {
|
pub extern "C" fn rust_stream_read(stream: *mut c_void, buffer: *mut u8, length: usize) -> usize {
|
||||||
let stream = unsafe { &mut *(stream as *mut RustStream<'_>) };
|
let stream = unsafe { &mut *(stream as *mut BridgeStream<'_>) };
|
||||||
let buffer = unsafe { std::slice::from_raw_parts_mut(buffer, length) };
|
let buffer = unsafe { std::slice::from_raw_parts_mut(buffer, length) };
|
||||||
stream.0.read(buffer).unwrap_or(0)
|
stream.0.read(buffer).unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_stream_write(stream: *mut c_void, data: *const u8, length: usize) {
|
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 stream = unsafe { &mut *(stream as *mut BridgeStream<'_>) };
|
||||||
let data = unsafe { std::slice::from_raw_parts(data, length) };
|
let data = unsafe { std::slice::from_raw_parts(data, length) };
|
||||||
stream.0.write_all(data).unwrap();
|
stream.0.write_all(data).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_stream_seek(stream: *mut c_void, offset: i64, whence: i32) {
|
pub extern "C" fn rust_stream_seek(stream: *mut c_void, offset: i64, whence: i32) {
|
||||||
let stream = unsafe { &mut *(stream as *mut RustStream<'_>) };
|
let stream = unsafe { &mut *(stream as *mut BridgeStream<'_>) };
|
||||||
let pos = match whence {
|
let pos = match whence {
|
||||||
0 => SeekFrom::Start(offset as u64),
|
0 => SeekFrom::Start(offset as u64),
|
||||||
1 => SeekFrom::Current(offset),
|
1 => SeekFrom::Current(offset),
|
||||||
|
@ -55,20 +53,20 @@ pub extern "C" fn rust_stream_seek(stream: *mut c_void, offset: i64, whence: i32
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_stream_truncate(stream: *mut c_void, length: i64) {
|
pub extern "C" fn rust_stream_truncate(stream: *mut c_void, length: i64) {
|
||||||
let stream = unsafe { &mut *(stream as *mut RustStream<'_>) };
|
let stream = unsafe { &mut *(stream as *mut BridgeStream<'_>) };
|
||||||
stream.0.seek(SeekFrom::Start(length as u64)).unwrap();
|
stream.0.seek(SeekFrom::Start(length as u64)).unwrap();
|
||||||
// TODO: Actually implement truncate once we have a better trait bound
|
// TODO: Actually implement truncate once we have a better trait bound
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_stream_tell(stream: *mut c_void) -> i64 {
|
pub extern "C" fn rust_stream_tell(stream: *mut c_void) -> i64 {
|
||||||
let stream = unsafe { &mut *(stream as *mut RustStream<'_>) };
|
let stream = unsafe { &mut *(stream as *mut BridgeStream<'_>) };
|
||||||
stream.0.seek(SeekFrom::Current(0)).unwrap() as i64
|
stream.0.seek(SeekFrom::Current(0)).unwrap() as i64
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_stream_length(stream: *mut c_void) -> i64 {
|
pub extern "C" fn rust_stream_length(stream: *mut c_void) -> i64 {
|
||||||
let stream = unsafe { &mut *(stream as *mut RustStream<'_>) };
|
let stream = unsafe { &mut *(stream as *mut BridgeStream<'_>) };
|
||||||
let current = stream.0.seek(SeekFrom::Current(0)).unwrap();
|
let current = stream.0.seek(SeekFrom::Current(0)).unwrap();
|
||||||
let end = stream.0.seek(SeekFrom::End(0)).unwrap();
|
let end = stream.0.seek(SeekFrom::End(0)).unwrap();
|
||||||
stream.0.seek(SeekFrom::Start(current)).unwrap();
|
stream.0.seek(SeekFrom::Start(current)).unwrap();
|
||||||
|
@ -77,6 +75,6 @@ pub extern "C" fn rust_stream_length(stream: *mut c_void) -> i64 {
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn rust_stream_is_readonly(stream: *const c_void) -> bool {
|
pub extern "C" fn rust_stream_is_readonly(stream: *const c_void) -> bool {
|
||||||
let stream = unsafe { &*(stream as *const RustStream<'_>) };
|
let stream = unsafe { &*(stream as *const BridgeStream<'_>) };
|
||||||
stream.0.is_readonly()
|
stream.0.is_readonly()
|
||||||
}
|
}
|
||||||
|
|
84
musikr/src/main/jni/src/taglib/tk.rs
Normal file
84
musikr/src/main/jni/src/taglib/tk.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
use std::{ffi::CStr, string::ToString};
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::bridge::{self, Property};
|
||||||
|
pub use super::bridge::{TString, TStringList, SimplePropertyMap};
|
||||||
|
|
||||||
|
impl SimplePropertyMap {
|
||||||
|
pub fn to_hashmap(&self) -> HashMap<String, Vec<String>> {
|
||||||
|
let cxx_vec = unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The value is a unique_ptr to a copied vector that is not dependent
|
||||||
|
// on the address of self.
|
||||||
|
let this = Pin::new_unchecked(self);
|
||||||
|
bridge::SimplePropertyMap_to_vector(this)
|
||||||
|
};
|
||||||
|
cxx_vec.iter().map(|property| property.to_tuple()).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
pub fn to_tuple(&self) -> (String, Vec<String>) {
|
||||||
|
unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The values returned are copied and thus not dependent on the address
|
||||||
|
// of self.
|
||||||
|
let this = Pin::new_unchecked(self);
|
||||||
|
let key = this.thisKey().to_string();
|
||||||
|
let value = this.thisValue().to_vec();
|
||||||
|
(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for TString {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
let c_str = unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The value returned are pointers and thus not dependent on the address
|
||||||
|
// of self.
|
||||||
|
let this: Pin<&TString> = Pin::new_unchecked(self);
|
||||||
|
this.thisToCString(true)
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This is a C-string returned by a C++ method guaranteed to have
|
||||||
|
// a null terminator.
|
||||||
|
// - This C-string is fully allocated and owned by the TagString instance,
|
||||||
|
// in a continous block from start to null terminator.
|
||||||
|
// - This C-string will be non-null even if empty.
|
||||||
|
// - This pointer will not be mutated before it's entirely copied into
|
||||||
|
// rust.
|
||||||
|
// - This C-string is copied to a rust string before TagString is destroyed.
|
||||||
|
CStr::from_ptr(c_str)
|
||||||
|
}
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TStringList {
|
||||||
|
pub fn to_vec(&self) -> Vec<String> {
|
||||||
|
let cxx_values = unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The value returned is a unique ptr to a copied vector that is not
|
||||||
|
// dependent on the address of self.
|
||||||
|
let this = Pin::new_unchecked(self);
|
||||||
|
bridge::StringList_to_vector(this)
|
||||||
|
};
|
||||||
|
cxx_values.iter().map(|value| value.to_string()).collect()
|
||||||
|
}
|
||||||
|
}
|
75
musikr/src/main/jni/src/taglib/xiph.rs
Normal file
75
musikr/src/main/jni/src/taglib/xiph.rs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
pub use super::bridge::{OpusFile, VorbisFile, FLACFile, XiphComment};
|
||||||
|
use super::tk::SimplePropertyMap;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
impl OpusFile {
|
||||||
|
pub fn xiph_comments(&self) -> Option<&XiphComment> {
|
||||||
|
let tag = unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The value is a pointer that does not depend on the address of self.
|
||||||
|
let this = Pin::new_unchecked(self);
|
||||||
|
this.opusThisTag()
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
// SAFETY: This pointer is a valid type, and can only used and accessed
|
||||||
|
// via this function and thus cannot be mutated, satisfying the aliasing rules.
|
||||||
|
tag.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VorbisFile {
|
||||||
|
pub fn xiph_comments(&self) -> Option<&XiphComment> {
|
||||||
|
let tag = unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The value is a pointer that does not depend on the address of self.
|
||||||
|
let this = Pin::new_unchecked(self);
|
||||||
|
this.vorbisThisTag()
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
// SAFETY: This pointer is a valid type, and can only used and accessed
|
||||||
|
// via this function and thus cannot be mutated, satisfying the aliasing rules.
|
||||||
|
tag.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FLACFile {
|
||||||
|
pub fn xiph_comments(&mut self) -> Option<&XiphComment> {
|
||||||
|
let tag = unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The value is a pointer that does not depend on the address of self.
|
||||||
|
let this = Pin::new_unchecked(self);
|
||||||
|
// SAFETY: This is a C++ FFI function ensured to call correctly.
|
||||||
|
this.flacThisXiphComment(false)
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
// SAFETY: This pointer is a valid type, and can only used and accessed
|
||||||
|
// via this function and thus cannot be mutated, satisfying the aliasing rules.
|
||||||
|
tag.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XiphComment {
|
||||||
|
pub fn field_list_map(&self) -> &SimplePropertyMap {
|
||||||
|
unsafe {
|
||||||
|
// SAFETY:
|
||||||
|
// - This pin is only used in this unsafe scope.
|
||||||
|
// - The pin is used as a C++ this pointer in the ffi call, which does
|
||||||
|
// not change address by C++ semantics.
|
||||||
|
// - The value is a reference that does not depend on the address of self.
|
||||||
|
let this = Pin::new_unchecked(self);
|
||||||
|
this.thisFieldListMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue