From 3ecdbf289b90596f54e8ca18df89f92627724937 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Wed, 12 Feb 2025 22:31:46 -0700 Subject: [PATCH] musikr: reorganize ffi module Still don't fully like the segregation, but now at least the bridge unsafety is fully isolated. --- musikr/src/main/jni/build.rs | 4 +- musikr/src/main/jni/shim/file_shim.cpp | 16 +- musikr/src/main/jni/shim/file_shim.hpp | 4 +- .../jni/src/{jni_stream.rs => jstream.rs} | 8 +- musikr/src/main/jni/src/lib.rs | 62 ++- musikr/src/main/jni/src/taglib/bridge.rs | 141 ++++++ musikr/src/main/jni/src/taglib/ffi.rs | 457 ------------------ musikr/src/main/jni/src/taglib/file.rs | 189 ++++++++ musikr/src/main/jni/src/taglib/mod.rs | 128 +---- musikr/src/main/jni/src/taglib/stream.rs | 28 +- musikr/src/main/jni/src/taglib/tk.rs | 84 ++++ musikr/src/main/jni/src/taglib/xiph.rs | 75 +++ 12 files changed, 562 insertions(+), 634 deletions(-) rename musikr/src/main/jni/src/{jni_stream.rs => jstream.rs} (95%) create mode 100644 musikr/src/main/jni/src/taglib/bridge.rs delete mode 100644 musikr/src/main/jni/src/taglib/ffi.rs create mode 100644 musikr/src/main/jni/src/taglib/file.rs create mode 100644 musikr/src/main/jni/src/taglib/tk.rs create mode 100644 musikr/src/main/jni/src/taglib/xiph.rs diff --git a/musikr/src/main/jni/build.rs b/musikr/src/main/jni/build.rs index 5c21e10e8..377c76e63 100644 --- a/musikr/src/main/jni/build.rs +++ b/musikr/src/main/jni/build.rs @@ -47,7 +47,7 @@ fn main() { } else { panic!("Unsupported Android target: {}", target); }; - + let clang_path = env::var("CLANG_PATH").expect("CLANG_PATH env var not set"); let toolchains_marker = "/toolchains"; 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"); // 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 .file("shim/iostream_shim.cpp") .file("shim/file_shim.cpp") diff --git a/musikr/src/main/jni/shim/file_shim.cpp b/musikr/src/main/jni/shim/file_shim.cpp index f9f293341..4f7d37032 100644 --- a/musikr/src/main/jni/shim/file_shim.cpp +++ b/musikr/src/main/jni/shim/file_shim.cpp @@ -33,14 +33,14 @@ namespace taglib_shim return dynamic_cast(file); } - TagLib::WavPack::File *File_asWavPack(TagLib::File *file) - { - return dynamic_cast(file); - } + // TagLib::WavPack::File *File_asWavPack(TagLib::File *file) + // { + // return dynamic_cast(file); + // } - TagLib::APE::File *File_asAPE(TagLib::File *file) - { - return dynamic_cast(file); - } + // TagLib::APE::File *File_asAPE(TagLib::File *file) + // { + // return dynamic_cast(file); + // } } // namespace taglib_shim \ No newline at end of file diff --git a/musikr/src/main/jni/shim/file_shim.hpp b/musikr/src/main/jni/shim/file_shim.hpp index 3b81ba16d..57b4706c9 100644 --- a/musikr/src/main/jni/shim/file_shim.hpp +++ b/musikr/src/main/jni/shim/file_shim.hpp @@ -23,7 +23,7 @@ namespace taglib_shim TagLib::FLAC::File *File_asFLAC(TagLib::File *file); TagLib::MP4::File *File_asMP4(TagLib::File *file); TagLib::RIFF::WAV::File *File_asWAV(TagLib::File *file); - TagLib::WavPack::File *File_asWavPack(TagLib::File *file); - TagLib::APE::File *File_asAPE(TagLib::File *file); + // TagLib::WavPack::File *File_asWavPack(TagLib::File *file); + // TagLib::APE::File *File_asAPE(TagLib::File *file); } // namespace taglib_shim \ No newline at end of file diff --git a/musikr/src/main/jni/src/jni_stream.rs b/musikr/src/main/jni/src/jstream.rs similarity index 95% rename from musikr/src/main/jni/src/jni_stream.rs rename to musikr/src/main/jni/src/jstream.rs index 2d719c1dd..4c4ace75a 100644 --- a/musikr/src/main/jni/src/jni_stream.rs +++ b/musikr/src/main/jni/src/jstream.rs @@ -1,4 +1,4 @@ -use crate::taglib::TagLibStream; +use crate::taglib::stream::IOStream; use jni::objects::{JObject, JValue}; use jni::JNIEnv; use std::io::{Read, Seek, SeekFrom, Write}; @@ -12,12 +12,12 @@ impl<'local, 'a> JInputStream<'local, 'a> { pub fn new( env: &'a mut JNIEnv<'local>, input: JObject<'local>, - ) -> Result { - Ok(JInputStream { env, input }) + ) -> Self { + Self { env, input } } } -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 diff --git a/musikr/src/main/jni/src/lib.rs b/musikr/src/main/jni/src/lib.rs index 3fc2f7e6e..352ae9059 100644 --- a/musikr/src/main/jni/src/lib.rs +++ b/musikr/src/main/jni/src/lib.rs @@ -2,10 +2,12 @@ use jni::objects::{JClass, JObject}; use jni::sys::jstring; use jni::JNIEnv; -mod jni_stream; mod taglib; +mod jstream; -use jni_stream::JInputStream; +use taglib::file::FileRef; + +use jstream::JInputStream; pub use taglib::*; #[no_mangle] @@ -15,29 +17,43 @@ pub extern "C" fn Java_org_oxycblt_musikr_metadata_MetadataJNI_openFile<'local>( input: JObject<'local>, ) -> jstring { // Create JInputStream from the Java input stream - let stream = match JInputStream::new(&mut env, input) { - Ok(stream) => stream, - Err(e) => { - let error = format!("Failed to create input stream: {}", e); - let error_str = env - .new_string(error) - .expect("Couldn't create error string!"); - return error_str.into_raw(); - } - }; + let stream = JInputStream::new(&mut env, input); + let file_ref = FileRef::new(stream); + // file_ref.file().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(), + // }); - // Create FileRef from the stream - let file_ref = match FileRef::from_stream(stream) { - Some(file_ref) => file_ref, - None => { - let error = "Failed to create File"; - let error_str = env - .new_string(error) - .expect("Couldn't create error string!"); - return error_str.into_raw(); - } - }; + // if let Some(vorbis_file) = file.as_vorbis() { + // let xiph_comments = vorbis_file + // .xiph_comments() + // .map(|comments| comments.field_list_map().to_hashmap()); + // } 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 let output = env.new_string("title").expect("Couldn't create string!"); output.into_raw() diff --git a/musikr/src/main/jni/src/taglib/bridge.rs b/musikr/src/main/jni/src/taglib/bridge.rs new file mode 100644 index 000000000..d2a8b8748 --- /dev/null +++ b/musikr/src/main/jni/src/taglib/bridge.rs @@ -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; + + #[namespace = "taglib_shim"] + type RustStream; + #[namespace = "taglib_shim"] + fn new_FileRef_from_stream(stream: UniquePtr) -> UniquePtr; + + #[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>; + + #[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>; + } +} + +pub use bridge_impl::*; diff --git a/musikr/src/main/jni/src/taglib/ffi.rs b/musikr/src/main/jni/src/taglib/ffi.rs deleted file mode 100644 index 2ab1df0d7..000000000 --- a/musikr/src/main/jni/src/taglib/ffi.rs +++ /dev/null @@ -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; - - #[namespace = "taglib_shim"] - type RustStream; - #[namespace = "taglib_shim"] - fn new_FileRef_from_stream(stream: UniquePtr) -> UniquePtr; - - #[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>; - - #[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>; - } -} - -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> { - 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) { - 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 { - 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() - } -} diff --git a/musikr/src/main/jni/src/taglib/file.rs b/musikr/src/main/jni/src/taglib/file.rs new file mode 100644 index 000000000..ff4f42781 --- /dev/null +++ b/musikr/src/main/jni/src/taglib/file.rs @@ -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 +} + +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() + } + } +} diff --git a/musikr/src/main/jni/src/taglib/mod.rs b/musikr/src/main/jni/src/taglib/mod.rs index 71a7671ec..201e282b5 100644 --- a/musikr/src/main/jni/src/taglib/mod.rs +++ b/musikr/src/main/jni/src/taglib/mod.rs @@ -1,124 +1,6 @@ -mod ffi; -mod stream; +mod bridge; -use ffi::bindings; -use std::collections::HashMap; -pub use stream::{RustStream, TagLibStream}; - -type XiphComments = HashMap>; - -pub enum File { - Unknown { - audio_properties: Option, - }, - MP3 { - audio_properties: Option, - }, - FLAC { - audio_properties: Option, - xiph_comments: Option, - }, - MP4 { - audio_properties: Option, - }, - OGG { - audio_properties: Option, - xiph_comments: Option, - }, - Opus { - audio_properties: Option, - xiph_comments: Option, - }, - WAV { - audio_properties: Option, - }, - WavPack { - audio_properties: Option, - }, - APE { - audio_properties: Option, - }, -} - -/// 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, -} - -impl FileRef { - /// Create a new FileRef from a stream implementing TagLibStream - pub fn from_stream<'a, T: TagLibStream + 'a>(stream: T) -> Option { - // 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 { - &self.file - } -} +pub mod file; +pub mod stream; +pub mod tk; +pub mod xiph; diff --git a/musikr/src/main/jni/src/taglib/stream.rs b/musikr/src/main/jni/src/taglib/stream.rs index 069266c40..e088e251f 100644 --- a/musikr/src/main/jni/src/taglib/stream.rs +++ b/musikr/src/main/jni/src/taglib/stream.rs @@ -2,25 +2,23 @@ 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 { +pub trait IOStream: Read + Write + Seek { fn name(&mut self) -> String; fn is_readonly(&self) -> bool; } -// Opaque type for C++ #[repr(C)] -pub struct RustStream<'a>(Box); +pub(super) struct BridgeStream<'a>(Box); -impl<'a> RustStream<'a> { - pub fn new(stream: T) -> Self { - RustStream(Box::new(stream)) +impl<'a> BridgeStream<'a> { + pub fn new(stream: T) -> Self { + BridgeStream(Box::new(stream)) } } #[no_mangle] 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(); // Note: This leaks memory, but TagLib only calls this once during construction // 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] 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) }; 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 stream = unsafe { &mut *(stream as *mut BridgeStream<'_>) }; 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, offset: i64, whence: i32) { - let stream = unsafe { &mut *(stream as *mut RustStream<'_>) }; + let stream = unsafe { &mut *(stream as *mut BridgeStream<'_>) }; let pos = match whence { 0 => SeekFrom::Start(offset as u64), 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] 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(); // 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<'_>) }; + let stream = unsafe { &mut *(stream as *mut BridgeStream<'_>) }; stream.0.seek(SeekFrom::Current(0)).unwrap() as i64 } #[no_mangle] 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 end = stream.0.seek(SeekFrom::End(0)).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] 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() } diff --git a/musikr/src/main/jni/src/taglib/tk.rs b/musikr/src/main/jni/src/taglib/tk.rs new file mode 100644 index 000000000..32d441ff6 --- /dev/null +++ b/musikr/src/main/jni/src/taglib/tk.rs @@ -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> { + 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) { + 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 { + 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() + } +} diff --git a/musikr/src/main/jni/src/taglib/xiph.rs b/musikr/src/main/jni/src/taglib/xiph.rs new file mode 100644 index 000000000..d41edf27d --- /dev/null +++ b/musikr/src/main/jni/src/taglib/xiph.rs @@ -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() + } + } +}