mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-07 01:09:54 +08:00
feat(native): sync yocto codes (#14243)
#### PR Dependency Tree * **PR #14243** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Batch management API for coordinated document mutations and change tracking. * New document accessors (IDs, state snapshots, change/delete set queries) and subscriber count. * **Chores** * Upgraded Rust edition across packages to 2024. * Repository-wide formatting, stylistic cleanups and test adjustments. * **Breaking Changes** * Removed the Node native bindings package and its JS/TS declarations and tests (no longer published/available). <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
name = "affine_native"
|
||||
version = "0.0.0"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
name = "affine_media_capture"
|
||||
version = "0.0.0"
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::hint::black_box;
|
||||
#[cfg(target_os = "macos")]
|
||||
use affine_media_capture::macos::audio_buffer::{mix_audio_samples, mix_audio_samples_scalar};
|
||||
#[cfg(target_os = "macos")]
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn generate_test_samples() -> [f32; 1024] {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::{io::Cursor, path::Path};
|
||||
|
||||
use napi::{
|
||||
bindgen_prelude::{AbortSignal, AsyncTask, Float32Array, Result, Status, Uint8Array},
|
||||
Task,
|
||||
bindgen_prelude::{AbortSignal, AsyncTask, Float32Array, Result, Status, Uint8Array},
|
||||
};
|
||||
use napi_derive::napi;
|
||||
use rubato::{Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType};
|
||||
@@ -26,9 +26,7 @@ fn decode<B: AsRef<[u8]> + Send + Sync + 'static>(
|
||||
|
||||
// Create a probe hint using the file extension
|
||||
let mut hint = Hint::new();
|
||||
if let Some(ext) =
|
||||
filename.and_then(|filename| Path::new(filename).extension().and_then(|ext| ext.to_str()))
|
||||
{
|
||||
if let Some(ext) = filename.and_then(|filename| Path::new(filename).extension().and_then(|ext| ext.to_str())) {
|
||||
hint.with_extension(ext);
|
||||
}
|
||||
|
||||
@@ -106,9 +104,7 @@ fn decode<B: AsRef<[u8]> + Send + Sync + 'static>(
|
||||
let mut waves_out = resampler
|
||||
.process(&waves_in, None)
|
||||
.map_err(|_| Error::Unsupported("Failed to run resampler"))?;
|
||||
output = waves_out
|
||||
.pop()
|
||||
.ok_or(Error::Unsupported("No resampled output found"))?;
|
||||
output = waves_out.pop().ok_or(Error::Unsupported("No resampled output found"))?;
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
|
||||
@@ -170,10 +170,7 @@ pub struct AudioBufferList {
|
||||
}
|
||||
|
||||
unsafe impl Encode for AudioBufferList {
|
||||
const ENCODING: Encoding = Encoding::Struct(
|
||||
"AudioBufferList",
|
||||
&[<u32>::ENCODING, <[AudioBuffer; 1]>::ENCODING],
|
||||
);
|
||||
const ENCODING: Encoding = Encoding::Struct("AudioBufferList", &[<u32>::ENCODING, <[AudioBuffer; 1]>::ENCODING]);
|
||||
}
|
||||
|
||||
unsafe impl RefEncode for AudioBufferList {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use coreaudio::sys::{kAudioTapPropertyFormat, AudioObjectID};
|
||||
use coreaudio::sys::{AudioObjectID, kAudioTapPropertyFormat};
|
||||
use objc2::{Encode, Encoding, RefEncode};
|
||||
|
||||
use crate::{error::CoreAudioError, utils::get_global_main_property};
|
||||
@@ -226,9 +226,8 @@ impl Display for AudioStreamDescription {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"AudioStreamBasicDescription {{ mSampleRate: {}, mFormatID: {:?}, mFormatFlags: {}, \
|
||||
mBytesPerPacket: {}, mFramesPerPacket: {}, mBytesPerFrame: {}, mChannelsPerFrame: {}, \
|
||||
mBitsPerChannel: {}, mReserved: {} }}",
|
||||
"AudioStreamBasicDescription {{ mSampleRate: {}, mFormatID: {:?}, mFormatFlags: {}, mBytesPerPacket: {}, \
|
||||
mFramesPerPacket: {}, mBytesPerFrame: {}, mChannelsPerFrame: {}, mBitsPerChannel: {}, mReserved: {} }}",
|
||||
self.0.mSampleRate,
|
||||
AudioFormatID::from(self.0.mFormatID),
|
||||
AudioFormatFlags(self.0.mFormatFlags),
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
use std::ptr;
|
||||
|
||||
use objc2::{
|
||||
msg_send,
|
||||
AllocAnyThread, msg_send,
|
||||
runtime::{AnyClass, AnyObject},
|
||||
AllocAnyThread,
|
||||
};
|
||||
use objc2_foundation::{NSDictionary, NSError, NSNumber, NSString, NSUInteger, NSURL};
|
||||
|
||||
use crate::{
|
||||
av_audio_format::AVAudioFormat, av_audio_pcm_buffer::AVAudioPCMBuffer, error::CoreAudioError,
|
||||
};
|
||||
use crate::{av_audio_format::AVAudioFormat, av_audio_pcm_buffer::AVAudioPCMBuffer, error::CoreAudioError};
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) struct AVAudioFile {
|
||||
@@ -32,10 +29,7 @@ impl AVAudioFile {
|
||||
&*NSString::from_str("AVNumberOfChannelsKey"),
|
||||
],
|
||||
&[
|
||||
NSNumber::initWithUnsignedInt(
|
||||
NSNumber::alloc(),
|
||||
format.audio_stream_basic_description.0.mFormatID,
|
||||
),
|
||||
NSNumber::initWithUnsignedInt(NSNumber::alloc(), format.audio_stream_basic_description.0.mFormatID),
|
||||
NSNumber::initWithDouble(NSNumber::alloc(), format.get_sample_rate()),
|
||||
NSNumber::initWithUnsignedInt(NSNumber::alloc(), format.get_channel_count()),
|
||||
],
|
||||
@@ -61,8 +55,7 @@ impl AVAudioFile {
|
||||
|
||||
pub(crate) fn write(&self, buffer: AVAudioPCMBuffer) -> Result<(), CoreAudioError> {
|
||||
let mut error: *mut NSError = ptr::null_mut();
|
||||
let success: bool =
|
||||
unsafe { msg_send![self.inner, writeFromBuffer: buffer.inner, error: &mut error] };
|
||||
let success: bool = unsafe { msg_send![self.inner, writeFromBuffer: buffer.inner, error: &mut error] };
|
||||
if !success {
|
||||
return Err(CoreAudioError::WriteAVAudioFileFailed);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use objc2::{
|
||||
msg_send,
|
||||
Encode, Encoding, RefEncode, msg_send,
|
||||
runtime::{AnyClass, AnyObject},
|
||||
Encode, Encoding, RefEncode,
|
||||
};
|
||||
|
||||
use crate::{audio_stream_basic_desc::AudioStreamDescription, error::CoreAudioError};
|
||||
@@ -37,11 +36,7 @@ unsafe impl Encode for AVAudioFormatRef {
|
||||
1,
|
||||
&Encoding::Struct(
|
||||
"AudioChannelDescription",
|
||||
&[
|
||||
Encoding::UInt,
|
||||
Encoding::UInt,
|
||||
Encoding::Array(3, &Encoding::Float),
|
||||
],
|
||||
&[Encoding::UInt, Encoding::UInt, Encoding::Array(3, &Encoding::Float)],
|
||||
),
|
||||
),
|
||||
Encoding::UInt,
|
||||
@@ -62,16 +57,13 @@ unsafe impl RefEncode for AVAudioFormatRef {
|
||||
|
||||
#[allow(unused)]
|
||||
impl AVAudioFormat {
|
||||
pub fn new(
|
||||
audio_stream_basic_description: AudioStreamDescription,
|
||||
) -> Result<Self, CoreAudioError> {
|
||||
pub fn new(audio_stream_basic_description: AudioStreamDescription) -> Result<Self, CoreAudioError> {
|
||||
let cls = AnyClass::get(c"AVAudioFormat").ok_or(CoreAudioError::AVAudioFormatClassNotFound)?;
|
||||
let obj: *mut AnyObject = unsafe { msg_send![cls, alloc] };
|
||||
if obj.is_null() {
|
||||
return Err(CoreAudioError::AllocAVAudioFormatFailed);
|
||||
}
|
||||
let obj: *mut AnyObject =
|
||||
unsafe { msg_send![obj, initWithStreamDescription: &audio_stream_basic_description.0] };
|
||||
let obj: *mut AnyObject = unsafe { msg_send![obj, initWithStreamDescription: &audio_stream_basic_description.0] };
|
||||
if obj.is_null() {
|
||||
return Err(CoreAudioError::InitAVAudioFormatFailed);
|
||||
}
|
||||
|
||||
@@ -13,12 +13,8 @@ pub(crate) struct AVAudioPCMBuffer {
|
||||
|
||||
#[allow(unused)]
|
||||
impl AVAudioPCMBuffer {
|
||||
pub(crate) fn new(
|
||||
audio_format: &AVAudioFormat,
|
||||
buffer_list: *const AudioBufferList,
|
||||
) -> Result<Self, CoreAudioError> {
|
||||
let cls =
|
||||
AnyClass::get(c"AVAudioPCMBuffer").ok_or(CoreAudioError::AVAudioPCMBufferClassNotFound)?;
|
||||
pub(crate) fn new(audio_format: &AVAudioFormat, buffer_list: *const AudioBufferList) -> Result<Self, CoreAudioError> {
|
||||
let cls = AnyClass::get(c"AVAudioPCMBuffer").ok_or(CoreAudioError::AVAudioPCMBufferClassNotFound)?;
|
||||
let obj: *mut AnyObject = unsafe { msg_send![cls, alloc] };
|
||||
if obj.is_null() {
|
||||
return Err(CoreAudioError::AllocAVAudioPCMBufferFailed);
|
||||
|
||||
@@ -4,9 +4,8 @@ use core_foundation::{
|
||||
};
|
||||
use coreaudio::sys::AudioObjectID;
|
||||
use objc2::{
|
||||
msg_send,
|
||||
AllocAnyThread, msg_send,
|
||||
runtime::{AnyClass, AnyObject},
|
||||
AllocAnyThread,
|
||||
};
|
||||
use objc2_foundation::{NSArray, NSNumber, NSString, NSUUID};
|
||||
|
||||
@@ -17,19 +16,14 @@ pub(crate) struct CATapDescription {
|
||||
}
|
||||
|
||||
impl CATapDescription {
|
||||
pub fn init_stereo_mixdown_of_processes(
|
||||
process: AudioObjectID,
|
||||
) -> std::result::Result<Self, CoreAudioError> {
|
||||
let cls =
|
||||
AnyClass::get(c"CATapDescription").ok_or(CoreAudioError::CATapDescriptionClassNotFound)?;
|
||||
pub fn init_stereo_mixdown_of_processes(process: AudioObjectID) -> std::result::Result<Self, CoreAudioError> {
|
||||
let cls = AnyClass::get(c"CATapDescription").ok_or(CoreAudioError::CATapDescriptionClassNotFound)?;
|
||||
let obj: *mut AnyObject = unsafe { msg_send![cls, alloc] };
|
||||
if obj.is_null() {
|
||||
return Err(CoreAudioError::AllocCATapDescriptionFailed);
|
||||
}
|
||||
let processes_array =
|
||||
NSArray::from_retained_slice(&[NSNumber::initWithUnsignedInt(NSNumber::alloc(), process)]);
|
||||
let obj: *mut AnyObject =
|
||||
unsafe { msg_send![obj, initStereoMixdownOfProcesses: &*processes_array] };
|
||||
let processes_array = NSArray::from_retained_slice(&[NSNumber::initWithUnsignedInt(NSNumber::alloc(), process)]);
|
||||
let obj: *mut AnyObject = unsafe { msg_send![obj, initStereoMixdownOfProcesses: &*processes_array] };
|
||||
if obj.is_null() {
|
||||
return Err(CoreAudioError::InitStereoMixdownOfProcessesFailed);
|
||||
}
|
||||
@@ -44,8 +38,7 @@ impl CATapDescription {
|
||||
pub fn init_stereo_global_tap_but_exclude_processes(
|
||||
processes: &[AudioObjectID],
|
||||
) -> std::result::Result<Self, CoreAudioError> {
|
||||
let cls =
|
||||
AnyClass::get(c"CATapDescription").ok_or(CoreAudioError::CATapDescriptionClassNotFound)?;
|
||||
let cls = AnyClass::get(c"CATapDescription").ok_or(CoreAudioError::CATapDescriptionClassNotFound)?;
|
||||
let obj: *mut AnyObject = unsafe { msg_send![cls, alloc] };
|
||||
if obj.is_null() {
|
||||
return Err(CoreAudioError::AllocCATapDescriptionFailed);
|
||||
@@ -57,8 +50,7 @@ impl CATapDescription {
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
);
|
||||
let obj: *mut AnyObject =
|
||||
unsafe { msg_send![obj, initStereoGlobalTapButExcludeProcesses: &*processes_array] };
|
||||
let obj: *mut AnyObject = unsafe { msg_send![obj, initStereoGlobalTapButExcludeProcesses: &*processes_array] };
|
||||
if obj.is_null() {
|
||||
return Err(CoreAudioError::InitStereoGlobalTapButExcludeProcessesFailed);
|
||||
}
|
||||
|
||||
@@ -60,14 +60,7 @@ impl ToCoreFoundation for CFDictionary<CFType, CFType> {
|
||||
|
||||
impl<T: ToCoreFoundation> ToCoreFoundation for Vec<T> {
|
||||
fn to_cf(&self) -> CFType {
|
||||
CFArray::from_CFTypes(
|
||||
self
|
||||
.iter()
|
||||
.map(|t| t.to_cf())
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
)
|
||||
.as_CFType()
|
||||
CFArray::from_CFTypes(self.iter().map(|t| t.to_cf()).collect::<Vec<_>>().as_slice()).as_CFType()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ use std::ptr;
|
||||
|
||||
use core_foundation::{base::TCFType, string::CFString};
|
||||
use coreaudio::sys::{
|
||||
kAudioDevicePropertyDeviceUID, kAudioObjectSystemObject, AudioDeviceID, AudioObjectID,
|
||||
CFStringRef,
|
||||
AudioDeviceID, AudioObjectID, CFStringRef, kAudioDevicePropertyDeviceUID, kAudioObjectSystemObject,
|
||||
};
|
||||
|
||||
use crate::{error::CoreAudioError, utils::get_global_main_property};
|
||||
@@ -13,20 +12,14 @@ pub(crate) fn get_device_uid(
|
||||
) -> std::result::Result<(AudioObjectID, CFString), CoreAudioError> {
|
||||
let system_audio_id = get_device_audio_id(device_id)?;
|
||||
let mut output_uid: CFStringRef = ptr::null_mut();
|
||||
get_global_main_property(
|
||||
system_audio_id,
|
||||
kAudioDevicePropertyDeviceUID,
|
||||
&mut output_uid,
|
||||
)?;
|
||||
get_global_main_property(system_audio_id, kAudioDevicePropertyDeviceUID, &mut output_uid)?;
|
||||
|
||||
Ok((system_audio_id, unsafe {
|
||||
CFString::wrap_under_create_rule(output_uid.cast())
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn get_device_audio_id(
|
||||
device_id: AudioDeviceID,
|
||||
) -> std::result::Result<AudioObjectID, CoreAudioError> {
|
||||
pub(crate) fn get_device_audio_id(device_id: AudioDeviceID) -> std::result::Result<AudioObjectID, CoreAudioError> {
|
||||
let mut system_output_id: AudioObjectID = 0;
|
||||
|
||||
get_global_main_property(kAudioObjectSystemObject, device_id, &mut system_output_id)?;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::{mem::MaybeUninit, ptr};
|
||||
|
||||
use coreaudio::sys::{
|
||||
kAudioHardwareNoError, kAudioHardwarePropertyProcessObjectList, kAudioObjectPropertyElementMain,
|
||||
kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject, AudioObjectGetPropertyData,
|
||||
AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress,
|
||||
AudioObjectPropertySelector,
|
||||
AudioObjectGetPropertyData, AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress,
|
||||
AudioObjectPropertySelector, kAudioHardwareNoError, kAudioHardwarePropertyProcessObjectList,
|
||||
kAudioObjectPropertyElementMain, kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject,
|
||||
};
|
||||
|
||||
use crate::error::CoreAudioError;
|
||||
@@ -17,15 +16,8 @@ pub fn audio_process_list() -> Result<Vec<AudioObjectID>, CoreAudioError> {
|
||||
};
|
||||
|
||||
let mut data_size = 0u32;
|
||||
let status = unsafe {
|
||||
AudioObjectGetPropertyDataSize(
|
||||
kAudioObjectSystemObject,
|
||||
&address,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
&mut data_size,
|
||||
)
|
||||
};
|
||||
let status =
|
||||
unsafe { AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &address, 0, ptr::null_mut(), &mut data_size) };
|
||||
|
||||
if status != kAudioHardwareNoError as i32 {
|
||||
return Err(CoreAudioError::GetProcessObjectListSizeFailed(status));
|
||||
@@ -63,9 +55,7 @@ pub fn get_process_property<T: Sized>(
|
||||
};
|
||||
|
||||
let mut data_size = 0u32;
|
||||
let status = unsafe {
|
||||
AudioObjectGetPropertyDataSize(object_id, &address, 0, ptr::null_mut(), &mut data_size)
|
||||
};
|
||||
let status = unsafe { AudioObjectGetPropertyDataSize(object_id, &address, 0, ptr::null_mut(), &mut data_size) };
|
||||
|
||||
if status != kAudioHardwareNoError as i32 {
|
||||
return Err(CoreAudioError::AudioObjectGetPropertyDataSizeFailed(status));
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::{
|
||||
ffi::c_void,
|
||||
ptr,
|
||||
sync::{
|
||||
atomic::{AtomicPtr, Ordering},
|
||||
Arc, LazyLock, RwLock,
|
||||
atomic::{AtomicPtr, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -14,11 +14,10 @@ use core_foundation::{
|
||||
string::{CFString, CFStringRef},
|
||||
};
|
||||
use coreaudio::sys::{
|
||||
kAudioHardwarePropertyProcessObjectList, kAudioObjectPropertyElementMain,
|
||||
AudioObjectAddPropertyListenerBlock, AudioObjectID, AudioObjectPropertyAddress,
|
||||
AudioObjectRemovePropertyListenerBlock, kAudioHardwarePropertyProcessObjectList, kAudioObjectPropertyElementMain,
|
||||
kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject, kAudioProcessPropertyBundleID,
|
||||
kAudioProcessPropertyIsRunning, kAudioProcessPropertyIsRunningInput, kAudioProcessPropertyPID,
|
||||
AudioObjectAddPropertyListenerBlock, AudioObjectID, AudioObjectPropertyAddress,
|
||||
AudioObjectRemovePropertyListenerBlock,
|
||||
};
|
||||
use libc;
|
||||
use napi::{
|
||||
@@ -27,9 +26,8 @@ use napi::{
|
||||
};
|
||||
use napi_derive::napi;
|
||||
use objc2::{
|
||||
msg_send,
|
||||
Encode, Encoding, msg_send,
|
||||
runtime::{AnyClass, AnyObject},
|
||||
Encode, Encoding,
|
||||
};
|
||||
use objc2_foundation::NSString;
|
||||
use screencapturekit::shareable_content::SCShareableContent;
|
||||
@@ -71,20 +69,16 @@ unsafe impl Encode for CGRect {
|
||||
const ENCODING: Encoding = Encoding::Struct("CGRect", &[<CGPoint>::ENCODING, <CGSize>::ENCODING]);
|
||||
}
|
||||
|
||||
static RUNNING_APPLICATIONS: LazyLock<
|
||||
RwLock<std::result::Result<Vec<AudioObjectID>, CoreAudioError>>,
|
||||
> = LazyLock::new(|| RwLock::new(audio_process_list()));
|
||||
static RUNNING_APPLICATIONS: LazyLock<RwLock<std::result::Result<Vec<AudioObjectID>, CoreAudioError>>> =
|
||||
LazyLock::new(|| RwLock::new(audio_process_list()));
|
||||
|
||||
type ApplicationStateChangedSubscriberMap =
|
||||
HashMap<AudioObjectID, HashMap<Uuid, Arc<ThreadsafeFunction<(), ()>>>>;
|
||||
type ApplicationStateChangedSubscriberMap = HashMap<AudioObjectID, HashMap<Uuid, Arc<ThreadsafeFunction<(), ()>>>>;
|
||||
|
||||
static APPLICATION_STATE_CHANGED_SUBSCRIBERS: LazyLock<
|
||||
RwLock<ApplicationStateChangedSubscriberMap>,
|
||||
> = LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
static APPLICATION_STATE_CHANGED_SUBSCRIBERS: LazyLock<RwLock<ApplicationStateChangedSubscriberMap>> =
|
||||
LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
static APPLICATION_STATE_CHANGED_LISTENER_BLOCKS: LazyLock<
|
||||
RwLock<HashMap<AudioObjectID, AtomicPtr<c_void>>>,
|
||||
> = LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
static APPLICATION_STATE_CHANGED_LISTENER_BLOCKS: LazyLock<RwLock<HashMap<AudioObjectID, AtomicPtr<c_void>>>> =
|
||||
LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
static NSRUNNING_APPLICATION_CLASS: LazyLock<Option<&'static AnyClass>> =
|
||||
LazyLock::new(|| AnyClass::get(c"NSRunningApplication"));
|
||||
@@ -155,14 +149,12 @@ impl ApplicationInfo {
|
||||
}
|
||||
|
||||
// If not available, try to get from the audio process property
|
||||
if self.object_id > 0 {
|
||||
if let Ok(bundle_id) =
|
||||
get_process_property::<CFStringRef>(&self.object_id, kAudioProcessPropertyBundleID)
|
||||
{
|
||||
// Safely convert CFStringRef to Rust String
|
||||
let cf_string = unsafe { CFString::wrap_under_get_rule(bundle_id) };
|
||||
return cf_string.to_string();
|
||||
}
|
||||
if self.object_id > 0
|
||||
&& let Ok(bundle_id) = get_process_property::<CFStringRef>(&self.object_id, kAudioProcessPropertyBundleID)
|
||||
{
|
||||
// Safely convert CFStringRef to Rust String
|
||||
let cf_string = unsafe { CFString::wrap_under_get_rule(bundle_id) };
|
||||
return cf_string.to_string();
|
||||
}
|
||||
|
||||
String::new()
|
||||
@@ -281,8 +273,7 @@ impl ApplicationInfo {
|
||||
let _: () = msg_send![properties, setObject: compression_value, forKey: &*compression_key];
|
||||
|
||||
// Get PNG data with properties
|
||||
let png_data: *mut AnyObject =
|
||||
msg_send![bitmap, representationUsingType: 4u64, properties: properties]; // 4 = PNG
|
||||
let png_data: *mut AnyObject = msg_send![bitmap, representationUsingType: 4u64, properties: properties]; // 4 = PNG
|
||||
|
||||
if png_data.is_null() {
|
||||
return Ok(Buffer::from(Vec::<u8>::new()));
|
||||
@@ -338,10 +329,7 @@ impl ApplicationListChangedSubscriber {
|
||||
match result {
|
||||
Ok(status) => {
|
||||
if status != 0 {
|
||||
return Err(Error::new(
|
||||
Status::GenericFailure,
|
||||
"Failed to remove property listener",
|
||||
));
|
||||
return Err(Error::new(Status::GenericFailure, "Failed to remove property listener"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -363,31 +351,31 @@ pub struct ApplicationStateChangedSubscriber {
|
||||
impl ApplicationStateChangedSubscriber {
|
||||
#[napi]
|
||||
pub fn unsubscribe(&self) {
|
||||
if let Ok(mut lock) = APPLICATION_STATE_CHANGED_SUBSCRIBERS.write() {
|
||||
if let Some(subscribers) = lock.get_mut(&self.object_id) {
|
||||
subscribers.remove(&self.id);
|
||||
if subscribers.is_empty() {
|
||||
lock.remove(&self.object_id);
|
||||
if let Some(listener_block) = APPLICATION_STATE_CHANGED_LISTENER_BLOCKS
|
||||
.write()
|
||||
.ok()
|
||||
.as_mut()
|
||||
.and_then(|map| map.remove(&self.object_id))
|
||||
{
|
||||
// Wrap in catch_unwind to prevent crashes during shutdown
|
||||
let _ = std::panic::catch_unwind(|| unsafe {
|
||||
AudioObjectRemovePropertyListenerBlock(
|
||||
self.object_id,
|
||||
&AudioObjectPropertyAddress {
|
||||
mSelector: kAudioProcessPropertyIsRunning,
|
||||
mScope: kAudioObjectPropertyScopeGlobal,
|
||||
mElement: kAudioObjectPropertyElementMain,
|
||||
},
|
||||
ptr::null_mut(),
|
||||
listener_block.load(Ordering::Relaxed),
|
||||
);
|
||||
});
|
||||
}
|
||||
if let Ok(mut lock) = APPLICATION_STATE_CHANGED_SUBSCRIBERS.write()
|
||||
&& let Some(subscribers) = lock.get_mut(&self.object_id)
|
||||
{
|
||||
subscribers.remove(&self.id);
|
||||
if subscribers.is_empty() {
|
||||
lock.remove(&self.object_id);
|
||||
if let Some(listener_block) = APPLICATION_STATE_CHANGED_LISTENER_BLOCKS
|
||||
.write()
|
||||
.ok()
|
||||
.as_mut()
|
||||
.and_then(|map| map.remove(&self.object_id))
|
||||
{
|
||||
// Wrap in catch_unwind to prevent crashes during shutdown
|
||||
let _ = std::panic::catch_unwind(|| unsafe {
|
||||
AudioObjectRemovePropertyListenerBlock(
|
||||
self.object_id,
|
||||
&AudioObjectPropertyAddress {
|
||||
mSelector: kAudioProcessPropertyIsRunning,
|
||||
mScope: kAudioObjectPropertyScopeGlobal,
|
||||
mElement: kAudioObjectPropertyElementMain,
|
||||
},
|
||||
ptr::null_mut(),
|
||||
listener_block.load(Ordering::Relaxed),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,9 +390,7 @@ pub struct ShareableContent {
|
||||
#[napi]
|
||||
impl ShareableContent {
|
||||
#[napi]
|
||||
pub fn on_application_list_changed(
|
||||
callback: ThreadsafeFunction<(), ()>,
|
||||
) -> Result<ApplicationListChangedSubscriber> {
|
||||
pub fn on_application_list_changed(callback: ThreadsafeFunction<(), ()>) -> Result<ApplicationListChangedSubscriber> {
|
||||
let callback_arc = Arc::new(callback);
|
||||
let callback_clone = callback_arc.clone();
|
||||
let callback_block: RcBlock<dyn Fn(u32, *mut c_void)> =
|
||||
@@ -442,10 +428,7 @@ impl ShareableContent {
|
||||
)
|
||||
};
|
||||
if status != 0 {
|
||||
return Err(Error::new(
|
||||
Status::GenericFailure,
|
||||
"Failed to add property listener",
|
||||
));
|
||||
return Err(Error::new(Status::GenericFailure, "Failed to add property listener"));
|
||||
}
|
||||
Ok(ApplicationListChangedSubscriber {
|
||||
listener_block: callback_block,
|
||||
@@ -480,16 +463,15 @@ impl ShareableContent {
|
||||
)
|
||||
};
|
||||
for address in addresses {
|
||||
if address.mSelector == kAudioProcessPropertyIsRunning {
|
||||
if let Some(subscribers) = APPLICATION_STATE_CHANGED_SUBSCRIBERS
|
||||
if address.mSelector == kAudioProcessPropertyIsRunning
|
||||
&& let Some(subscribers) = APPLICATION_STATE_CHANGED_SUBSCRIBERS
|
||||
.read()
|
||||
.ok()
|
||||
.as_ref()
|
||||
.and_then(|map| map.get(&object_id))
|
||||
{
|
||||
for callback in subscribers.values() {
|
||||
callback.call(Ok(()), ThreadsafeFunctionCallMode::NonBlocking);
|
||||
}
|
||||
{
|
||||
for callback in subscribers.values() {
|
||||
callback.call(Ok(()), ThreadsafeFunctionCallMode::NonBlocking);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -501,18 +483,10 @@ impl ShareableContent {
|
||||
};
|
||||
let listener_block = &*list_change as *const Block<dyn Fn(u32, *mut c_void)>;
|
||||
let status = unsafe {
|
||||
AudioObjectAddPropertyListenerBlock(
|
||||
object_id,
|
||||
&address,
|
||||
ptr::null_mut(),
|
||||
listener_block.cast_mut().cast(),
|
||||
)
|
||||
AudioObjectAddPropertyListenerBlock(object_id, &address, ptr::null_mut(), listener_block.cast_mut().cast())
|
||||
};
|
||||
if status != 0 {
|
||||
return Err(Error::new(
|
||||
Status::GenericFailure,
|
||||
"Failed to add property listener",
|
||||
));
|
||||
return Err(Error::new(Status::GenericFailure, "Failed to add property listener"));
|
||||
}
|
||||
let subscribers = {
|
||||
let mut map = HashMap::new();
|
||||
@@ -659,16 +633,16 @@ impl ShareableContent {
|
||||
}
|
||||
|
||||
// Find the audio object ID for this process
|
||||
if let Ok(app_list) = RUNNING_APPLICATIONS.read() {
|
||||
if let Ok(app_list) = app_list.as_ref() {
|
||||
for object_id in app_list {
|
||||
let pid = get_process_property(object_id, kAudioProcessPropertyPID).unwrap_or(-1);
|
||||
if pid == process_id as i32 {
|
||||
// Check if the process is actively using input (microphone)
|
||||
match get_process_property(object_id, kAudioProcessPropertyIsRunningInput) {
|
||||
Ok(is_running) => return Ok(is_running),
|
||||
Err(_) => continue,
|
||||
}
|
||||
if let Ok(app_list) = RUNNING_APPLICATIONS.read()
|
||||
&& let Ok(app_list) = app_list.as_ref()
|
||||
{
|
||||
for object_id in app_list {
|
||||
let pid = get_process_property(object_id, kAudioProcessPropertyPID).unwrap_or(-1);
|
||||
if pid == process_id as i32 {
|
||||
// Check if the process is actively using input (microphone)
|
||||
match get_process_property(object_id, kAudioProcessPropertyIsRunningInput) {
|
||||
Ok(is_running) => return Ok(is_running),
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,20 +8,17 @@ use core_foundation::{
|
||||
uuid::CFUUID,
|
||||
};
|
||||
use coreaudio::sys::{
|
||||
kAudioAggregateDeviceClockDeviceKey, kAudioAggregateDeviceIsPrivateKey,
|
||||
kAudioAggregateDeviceIsStackedKey, kAudioAggregateDeviceMainSubDeviceKey,
|
||||
kAudioAggregateDeviceNameKey, kAudioAggregateDeviceSubDeviceListKey,
|
||||
kAudioAggregateDeviceTapAutoStartKey, kAudioAggregateDeviceTapListKey,
|
||||
kAudioAggregateDeviceUIDKey, kAudioDevicePropertyDeviceIsAlive,
|
||||
kAudioDevicePropertyNominalSampleRate, kAudioHardwareBadDeviceError,
|
||||
kAudioHardwareBadStreamError, kAudioHardwareNoError, kAudioHardwarePropertyDefaultInputDevice,
|
||||
kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyElementMain,
|
||||
kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject, kAudioSubDeviceUIDKey,
|
||||
kAudioSubTapUIDKey, AudioDeviceCreateIOProcIDWithBlock, AudioDeviceDestroyIOProcID,
|
||||
AudioDeviceIOProcID, AudioDeviceStart, AudioDeviceStop, AudioHardwareCreateAggregateDevice,
|
||||
AudioHardwareDestroyAggregateDevice, AudioObjectAddPropertyListenerBlock,
|
||||
AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress,
|
||||
AudioObjectRemovePropertyListenerBlock, AudioTimeStamp, OSStatus,
|
||||
AudioDeviceCreateIOProcIDWithBlock, AudioDeviceDestroyIOProcID, AudioDeviceIOProcID, AudioDeviceStart,
|
||||
AudioDeviceStop, AudioHardwareCreateAggregateDevice, AudioHardwareDestroyAggregateDevice,
|
||||
AudioObjectAddPropertyListenerBlock, AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress,
|
||||
AudioObjectRemovePropertyListenerBlock, AudioTimeStamp, OSStatus, kAudioAggregateDeviceClockDeviceKey,
|
||||
kAudioAggregateDeviceIsPrivateKey, kAudioAggregateDeviceIsStackedKey, kAudioAggregateDeviceMainSubDeviceKey,
|
||||
kAudioAggregateDeviceNameKey, kAudioAggregateDeviceSubDeviceListKey, kAudioAggregateDeviceTapAutoStartKey,
|
||||
kAudioAggregateDeviceTapListKey, kAudioAggregateDeviceUIDKey, kAudioDevicePropertyDeviceIsAlive,
|
||||
kAudioDevicePropertyNominalSampleRate, kAudioHardwareBadDeviceError, kAudioHardwareBadStreamError,
|
||||
kAudioHardwareNoError, kAudioHardwarePropertyDefaultInputDevice, kAudioHardwarePropertyDefaultOutputDevice,
|
||||
kAudioObjectPropertyElementMain, kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject, kAudioSubDeviceUIDKey,
|
||||
kAudioSubTapUIDKey,
|
||||
};
|
||||
use napi::{
|
||||
bindgen_prelude::{Float32Array, Result, Status},
|
||||
@@ -41,11 +38,8 @@ use crate::{
|
||||
utils::{cfstring_from_bytes_with_nul, get_global_main_property},
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
fn AudioHardwareCreateProcessTap(
|
||||
inDescription: *mut AnyObject,
|
||||
outTapID: *mut AudioObjectID,
|
||||
) -> OSStatus;
|
||||
unsafe extern "C" {
|
||||
fn AudioHardwareCreateProcessTap(inDescription: *mut AnyObject, outTapID: *mut AudioObjectID) -> OSStatus;
|
||||
|
||||
fn AudioHardwareDestroyProcessTap(tapID: AudioObjectID) -> OSStatus;
|
||||
}
|
||||
@@ -80,12 +74,10 @@ impl AggregateDevice {
|
||||
return Err(CoreAudioError::CreateProcessTapFailed(status).into());
|
||||
}
|
||||
|
||||
let (input_device_id, default_input_uid) =
|
||||
get_device_uid(kAudioHardwarePropertyDefaultInputDevice)?;
|
||||
let (input_device_id, default_input_uid) = get_device_uid(kAudioHardwarePropertyDefaultInputDevice)?;
|
||||
|
||||
// Get the default output device ID
|
||||
let (output_device_id, output_device_uid) =
|
||||
get_device_uid(kAudioHardwarePropertyDefaultOutputDevice)?;
|
||||
let (output_device_id, output_device_uid) = get_device_uid(kAudioHardwarePropertyDefaultOutputDevice)?;
|
||||
let description_dict = Self::create_aggregate_description(
|
||||
tap_id,
|
||||
tap_description.get_uuid()?,
|
||||
@@ -96,10 +88,7 @@ impl AggregateDevice {
|
||||
let mut aggregate_device_id: AudioObjectID = 0;
|
||||
|
||||
let status = unsafe {
|
||||
AudioHardwareCreateAggregateDevice(
|
||||
description_dict.as_concrete_TypeRef().cast(),
|
||||
&mut aggregate_device_id,
|
||||
)
|
||||
AudioHardwareCreateAggregateDevice(description_dict.as_concrete_TypeRef().cast(), &mut aggregate_device_id)
|
||||
};
|
||||
|
||||
if status != 0 {
|
||||
@@ -119,8 +108,7 @@ impl AggregateDevice {
|
||||
|
||||
pub fn create_global_tap_but_exclude_processes(processes: &[AudioObjectID]) -> Result<Self> {
|
||||
let mut tap_id: AudioObjectID = 0;
|
||||
let tap_description =
|
||||
CATapDescription::init_stereo_global_tap_but_exclude_processes(processes)?;
|
||||
let tap_description = CATapDescription::init_stereo_global_tap_but_exclude_processes(processes)?;
|
||||
let status = unsafe { AudioHardwareCreateProcessTap(tap_description.inner, &mut tap_id) };
|
||||
|
||||
if status != 0 {
|
||||
@@ -128,12 +116,10 @@ impl AggregateDevice {
|
||||
}
|
||||
|
||||
// Get the default input device (microphone) UID and ID
|
||||
let (input_device_id, default_input_uid) =
|
||||
get_device_uid(kAudioHardwarePropertyDefaultInputDevice)?;
|
||||
let (input_device_id, default_input_uid) = get_device_uid(kAudioHardwarePropertyDefaultInputDevice)?;
|
||||
|
||||
// Get the default output device ID
|
||||
let (output_device_id, output_device_uid) =
|
||||
get_device_uid(kAudioHardwarePropertyDefaultOutputDevice)?;
|
||||
let (output_device_id, output_device_uid) = get_device_uid(kAudioHardwarePropertyDefaultOutputDevice)?;
|
||||
|
||||
let description_dict = Self::create_aggregate_description(
|
||||
tap_id,
|
||||
@@ -145,10 +131,7 @@ impl AggregateDevice {
|
||||
let mut aggregate_device_id: AudioObjectID = 0;
|
||||
|
||||
let status = unsafe {
|
||||
AudioHardwareCreateAggregateDevice(
|
||||
description_dict.as_concrete_TypeRef().cast(),
|
||||
&mut aggregate_device_id,
|
||||
)
|
||||
AudioHardwareCreateAggregateDevice(description_dict.as_concrete_TypeRef().cast(), &mut aggregate_device_id)
|
||||
};
|
||||
|
||||
// Check the status and return the appropriate result
|
||||
@@ -184,11 +167,7 @@ impl AggregateDevice {
|
||||
|
||||
fn get_aggregate_device_stats(&self) -> Result<AudioStats> {
|
||||
let mut sample_rate: f64 = 0.0;
|
||||
get_global_main_property(
|
||||
self.id,
|
||||
kAudioDevicePropertyNominalSampleRate,
|
||||
&mut sample_rate,
|
||||
)?;
|
||||
get_global_main_property(self.id, kAudioDevicePropertyNominalSampleRate, &mut sample_rate)?;
|
||||
|
||||
let audio_stats = AudioStats {
|
||||
sample_rate,
|
||||
@@ -270,9 +249,7 @@ impl AggregateDevice {
|
||||
// Use the consistent stats for the stream object returned
|
||||
let audio_stats_for_stream = current_audio_stats;
|
||||
|
||||
let in_io_block: RcBlock<
|
||||
dyn Fn(*mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut c_void) -> i32,
|
||||
>;
|
||||
let in_io_block: RcBlock<dyn Fn(*mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut c_void) -> i32>;
|
||||
{
|
||||
in_io_block = RcBlock::new(
|
||||
move |_in_now: *mut c_void,
|
||||
@@ -286,9 +263,7 @@ impl AggregateDevice {
|
||||
if *mSampleTime < 0.0 {
|
||||
return kAudioHardwareNoError as i32;
|
||||
}
|
||||
let Ok(dua_audio_buffer_list) =
|
||||
(unsafe { InputAndOutputAudioBufferList::from_raw(in_input_data) })
|
||||
else {
|
||||
let Ok(dua_audio_buffer_list) = (unsafe { InputAndOutputAudioBufferList::from_raw(in_input_data) }) else {
|
||||
return kAudioHardwareBadDeviceError as i32;
|
||||
};
|
||||
|
||||
@@ -301,10 +276,7 @@ impl AggregateDevice {
|
||||
};
|
||||
|
||||
// Send the processed audio data to JavaScript
|
||||
audio_stream_callback.call(
|
||||
Ok(mixed_samples.into()),
|
||||
ThreadsafeFunctionCallMode::NonBlocking,
|
||||
);
|
||||
audio_stream_callback.call(Ok(mixed_samples.into()), ThreadsafeFunctionCallMode::NonBlocking);
|
||||
|
||||
kAudioHardwareNoError as i32
|
||||
},
|
||||
@@ -316,10 +288,7 @@ impl AggregateDevice {
|
||||
&mut in_proc_id,
|
||||
self.id,
|
||||
dispatch2::DispatchRetained::as_ptr(&queue).as_ptr().cast(),
|
||||
(&*in_io_block
|
||||
as *const Block<
|
||||
dyn Fn(*mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut c_void) -> i32,
|
||||
>)
|
||||
(&*in_io_block as *const Block<dyn Fn(*mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut c_void) -> i32>)
|
||||
.cast_mut()
|
||||
.cast(),
|
||||
)
|
||||
@@ -374,34 +343,16 @@ impl AggregateDevice {
|
||||
let mut cf_dict_builder = CFDictionaryBuilder::new();
|
||||
|
||||
cf_dict_builder
|
||||
.add(
|
||||
kAudioAggregateDeviceNameKey.as_slice(),
|
||||
aggregate_device_name,
|
||||
)
|
||||
.add(
|
||||
kAudioAggregateDeviceUIDKey.as_slice(),
|
||||
aggregate_device_uid_string,
|
||||
)
|
||||
.add(
|
||||
kAudioAggregateDeviceMainSubDeviceKey.as_slice(),
|
||||
&output_device_id,
|
||||
)
|
||||
.add(kAudioAggregateDeviceNameKey.as_slice(), aggregate_device_name)
|
||||
.add(kAudioAggregateDeviceUIDKey.as_slice(), aggregate_device_uid_string)
|
||||
.add(kAudioAggregateDeviceMainSubDeviceKey.as_slice(), &output_device_id)
|
||||
.add(kAudioAggregateDeviceIsPrivateKey.as_slice(), true)
|
||||
// can't be stacked because we're using a tap
|
||||
.add(kAudioAggregateDeviceIsStackedKey.as_slice(), false)
|
||||
.add(kAudioAggregateDeviceTapAutoStartKey.as_slice(), true)
|
||||
.add(
|
||||
kAudioAggregateDeviceSubDeviceListKey.as_slice(),
|
||||
capture_device_list,
|
||||
)
|
||||
.add(
|
||||
kAudioAggregateDeviceClockDeviceKey.as_slice(),
|
||||
input_device_id,
|
||||
)
|
||||
.add(
|
||||
kAudioAggregateDeviceTapListKey.as_slice(),
|
||||
vec![tap_device_dict],
|
||||
);
|
||||
.add(kAudioAggregateDeviceSubDeviceListKey.as_slice(), capture_device_list)
|
||||
.add(kAudioAggregateDeviceClockDeviceKey.as_slice(), input_device_id)
|
||||
.add(kAudioAggregateDeviceTapListKey.as_slice(), vec![tap_device_dict]);
|
||||
|
||||
Ok(cf_dict_builder.build())
|
||||
}
|
||||
@@ -627,18 +578,13 @@ impl AggregateDeviceManager {
|
||||
|
||||
// Start the initial stream
|
||||
// Pass the initially determined consistent audio stats
|
||||
let original_audio_stats = self
|
||||
.device
|
||||
.get_aggregate_device_stats()
|
||||
.unwrap_or(AudioStats {
|
||||
sample_rate: 48000.0, // Match fallback in setup_device_change_listeners
|
||||
channels: 2,
|
||||
});
|
||||
let original_audio_stats = self.device.get_aggregate_device_stats().unwrap_or(AudioStats {
|
||||
sample_rate: 48000.0, // Match fallback in setup_device_change_listeners
|
||||
channels: 2,
|
||||
});
|
||||
self.original_audio_stats = Some(original_audio_stats); // Store for listener use
|
||||
|
||||
let initial_audio_tap_stream = self
|
||||
.device
|
||||
.start(audio_stream_callback.clone(), original_audio_stats)?; // Pass clone of callback
|
||||
let initial_audio_tap_stream = self.device.start(audio_stream_callback.clone(), original_audio_stats)?; // Pass clone of callback
|
||||
|
||||
// Setup device change listeners AFTER getting initial stats and stream
|
||||
self.setup_device_change_listeners()?;
|
||||
@@ -679,70 +625,68 @@ impl AggregateDeviceManager {
|
||||
};
|
||||
|
||||
// Create a block that will handle device changes
|
||||
let device_changed_block = RcBlock::new(
|
||||
move |_in_number_addresses: u32, _in_addresses: *mut c_void| {
|
||||
// Skip if we don't have all required information
|
||||
let Some(stream_mutex) = stream_arc.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let Some(callback) = callback_arc.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let device_changed_block = RcBlock::new(move |_in_number_addresses: u32, _in_addresses: *mut c_void| {
|
||||
// Skip if we don't have all required information
|
||||
let Some(stream_mutex) = stream_arc.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let Some(callback) = callback_arc.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Try to lock the stream mutex
|
||||
let Ok(mut stream_guard) = stream_mutex.lock() else {
|
||||
return;
|
||||
};
|
||||
// Try to lock the stream mutex
|
||||
let Ok(mut stream_guard) = stream_mutex.lock() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Create a new device with updated default devices
|
||||
let result: Result<AggregateDevice> = {
|
||||
if is_app_specific {
|
||||
if let Some(id) = app_id {
|
||||
// For device change listener, we need to create a minimal ApplicationInfo
|
||||
// We don't have the name here, so we'll use an empty string
|
||||
let app = ApplicationInfo::new(id as i32, String::new(), id);
|
||||
AggregateDevice::new(&app)
|
||||
} else {
|
||||
Err(CoreAudioError::CreateProcessTapFailed(0).into())
|
||||
}
|
||||
// Create a new device with updated default devices
|
||||
let result: Result<AggregateDevice> = {
|
||||
if is_app_specific {
|
||||
if let Some(id) = app_id {
|
||||
// For device change listener, we need to create a minimal ApplicationInfo
|
||||
// We don't have the name here, so we'll use an empty string
|
||||
let app = ApplicationInfo::new(id as i32, String::new(), id);
|
||||
AggregateDevice::new(&app)
|
||||
} else {
|
||||
AggregateDevice::create_global_tap_but_exclude_processes(&excluded_processes)
|
||||
Err(CoreAudioError::CreateProcessTapFailed(0).into())
|
||||
}
|
||||
};
|
||||
} else {
|
||||
AggregateDevice::create_global_tap_but_exclude_processes(&excluded_processes)
|
||||
}
|
||||
};
|
||||
|
||||
// If we successfully created a new device, stop the old stream and start a new
|
||||
// one
|
||||
match result {
|
||||
Ok(mut new_device) => {
|
||||
// Stop and drop the old stream if it exists
|
||||
if let Some(mut old_stream) = stream_guard.take() {
|
||||
// Explicitly drop the old stream's Box before creating the new device.
|
||||
// The drop implementation handles cleanup.
|
||||
// We call stop() directly.
|
||||
let stop_result = old_stream.stop();
|
||||
match stop_result {
|
||||
Ok(_) => {}
|
||||
Err(e) => println!("DEBUG: Error stopping old stream (proceeding anyway): {e}"),
|
||||
};
|
||||
drop(old_stream); // Ensure it's dropped now
|
||||
}
|
||||
|
||||
match new_device.start(callback.clone(), original_audio_stats) {
|
||||
Ok(new_stream) => {
|
||||
// Use the existing stream_guard which already holds the lock
|
||||
*stream_guard = Some(new_stream);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("DEBUG: Failed to start new stream: {e}");
|
||||
}
|
||||
}
|
||||
// If we successfully created a new device, stop the old stream and start a new
|
||||
// one
|
||||
match result {
|
||||
Ok(mut new_device) => {
|
||||
// Stop and drop the old stream if it exists
|
||||
if let Some(mut old_stream) = stream_guard.take() {
|
||||
// Explicitly drop the old stream's Box before creating the new device.
|
||||
// The drop implementation handles cleanup.
|
||||
// We call stop() directly.
|
||||
let stop_result = old_stream.stop();
|
||||
match stop_result {
|
||||
Ok(_) => {}
|
||||
Err(e) => println!("DEBUG: Error stopping old stream (proceeding anyway): {e}"),
|
||||
};
|
||||
drop(old_stream); // Ensure it's dropped now
|
||||
}
|
||||
Err(e) => {
|
||||
println!("DEBUG: Failed to create new device: {e}");
|
||||
|
||||
match new_device.start(callback.clone(), original_audio_stats) {
|
||||
Ok(new_stream) => {
|
||||
// Use the existing stream_guard which already holds the lock
|
||||
*stream_guard = Some(new_stream);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("DEBUG: Failed to start new stream: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
Err(e) => {
|
||||
println!("DEBUG: Failed to create new device: {e}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Create pointers to the device_changed_block that can be used in C functions
|
||||
let block_ptr = &*device_changed_block as *const Block<dyn Fn(u32, *mut c_void)>;
|
||||
@@ -1026,9 +970,7 @@ impl AudioCaptureSession {
|
||||
if let Some(manager) = &self.manager {
|
||||
manager
|
||||
.get_current_actual_sample_rate()? // Propagate CoreAudioError
|
||||
.ok_or_else(|| {
|
||||
napi::Error::from_reason("No active audio stream to get actual sample rate from")
|
||||
})
|
||||
.ok_or_else(|| napi::Error::from_reason("No active audio stream to get actual sample rate from"))
|
||||
} else if let Some(cached_rate) = self.sample_rate {
|
||||
// Return cached sample rate as the best approximation when session is stopped
|
||||
Ok(cached_rate)
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::{cell::RefCell, collections::HashMap, ffi::c_void, mem::size_of};
|
||||
|
||||
use core_foundation::string::CFString;
|
||||
use coreaudio::sys::{
|
||||
kAudioObjectPropertyElementMain, kAudioObjectPropertyScopeGlobal, AudioObjectGetPropertyData,
|
||||
AudioObjectID, AudioObjectPropertyAddress,
|
||||
AudioObjectGetPropertyData, AudioObjectID, AudioObjectPropertyAddress, kAudioObjectPropertyElementMain,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
};
|
||||
use rubato::{FastFixedIn, PolynomialDegree, Resampler};
|
||||
|
||||
@@ -29,7 +29,7 @@ impl BufferedResampler {
|
||||
let ratio = to_sr / from_sr;
|
||||
let resampler = FastFixedIn::<f32>::new(
|
||||
ratio,
|
||||
1.0, // max_resample_ratio_relative (must be >= 1.0, use 1.0 for fixed ratio)
|
||||
1.0, // max_resample_ratio_relative (must be >= 1.0, use 1.0 for fixed ratio)
|
||||
PolynomialDegree::Linear, // Use Linear interpolation quality
|
||||
RESAMPLER_INPUT_CHUNK,
|
||||
channels,
|
||||
@@ -58,9 +58,7 @@ impl BufferedResampler {
|
||||
// Drain exactly one chunk per channel
|
||||
let mut chunk: Vec<Vec<f32>> = Vec::with_capacity(self.channels);
|
||||
for ch in 0..self.channels {
|
||||
let tail = self.fifo[ch]
|
||||
.drain(..RESAMPLER_INPUT_CHUNK)
|
||||
.collect::<Vec<_>>();
|
||||
let tail = self.fifo[ch].drain(..RESAMPLER_INPUT_CHUNK).collect::<Vec<_>>();
|
||||
chunk.push(tail);
|
||||
}
|
||||
|
||||
@@ -171,20 +169,14 @@ pub fn process_audio_frame(
|
||||
if current_sample_rate != target_sample_rate {
|
||||
// Use (or create) a persistent BufferedResampler
|
||||
|
||||
let out_vec = RESAMPLER_CACHE.with(|cache| {
|
||||
RESAMPLER_CACHE.with(|cache| {
|
||||
let mut map = cache.borrow_mut();
|
||||
let key = (
|
||||
current_sample_rate as u32,
|
||||
target_sample_rate as u32,
|
||||
2usize,
|
||||
);
|
||||
let key = (current_sample_rate as u32, target_sample_rate as u32, 2usize);
|
||||
let resampler = map
|
||||
.entry(key)
|
||||
.or_insert_with(|| BufferedResampler::new(current_sample_rate, target_sample_rate, 2));
|
||||
resampler.feed(&[left, right])
|
||||
});
|
||||
|
||||
out_vec
|
||||
})
|
||||
} else {
|
||||
// No resampling needed, just interleave existing left/right data
|
||||
let mut interleaved: Vec<f32> = Vec::with_capacity(left.len() * 2);
|
||||
@@ -202,11 +194,7 @@ pub fn process_audio_frame(
|
||||
if current_sample_rate != target_sample_rate {
|
||||
let out_vec = RESAMPLER_CACHE.with(|cache| {
|
||||
let mut map = cache.borrow_mut();
|
||||
let key = (
|
||||
current_sample_rate as u32,
|
||||
target_sample_rate as u32,
|
||||
1usize,
|
||||
);
|
||||
let key = (current_sample_rate as u32, target_sample_rate as u32, 1usize);
|
||||
let resampler = map
|
||||
.entry(key)
|
||||
.or_insert_with(|| BufferedResampler::new(current_sample_rate, target_sample_rate, 1));
|
||||
|
||||
@@ -2,21 +2,21 @@ use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
thread::JoinHandle,
|
||||
};
|
||||
|
||||
use cpal::{
|
||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||
SampleRate,
|
||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||
};
|
||||
use crossbeam_channel::unbounded;
|
||||
use napi::{
|
||||
Error, Status,
|
||||
bindgen_prelude::{Float32Array, Result},
|
||||
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
|
||||
Error, Status,
|
||||
};
|
||||
use napi_derive::napi;
|
||||
use rubato::{FastFixedIn, PolynomialDegree, Resampler};
|
||||
@@ -95,11 +95,7 @@ thread_local! {
|
||||
static RESAMPLER_CACHE: RefCell<HashMap<(u32, u32, usize), BufferedResampler>> = RefCell::new(HashMap::new());
|
||||
}
|
||||
|
||||
fn process_audio_with_resampler(
|
||||
samples: Vec<f32>,
|
||||
from_sample_rate: u32,
|
||||
to_sample_rate: u32,
|
||||
) -> Vec<f32> {
|
||||
fn process_audio_with_resampler(samples: Vec<f32>, from_sample_rate: u32, to_sample_rate: u32) -> Vec<f32> {
|
||||
if from_sample_rate == to_sample_rate {
|
||||
return samples;
|
||||
}
|
||||
@@ -220,16 +216,13 @@ impl Drop for AudioCaptureSession {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_recording(
|
||||
audio_buffer_callback: ThreadsafeFunction<Float32Array, ()>,
|
||||
) -> Result<AudioCaptureSession> {
|
||||
pub fn start_recording(audio_buffer_callback: ThreadsafeFunction<Float32Array, ()>) -> Result<AudioCaptureSession> {
|
||||
let available_hosts = cpal::available_hosts();
|
||||
let host_id = available_hosts
|
||||
.first()
|
||||
.ok_or_else(|| Error::new(Status::GenericFailure, "No CPAL hosts available"))?;
|
||||
|
||||
let host =
|
||||
cpal::host_from_id(*host_id).map_err(|e| Error::new(Status::GenericFailure, format!("{e}")))?;
|
||||
let host = cpal::host_from_id(*host_id).map_err(|e| Error::new(Status::GenericFailure, format!("{e}")))?;
|
||||
|
||||
let mic = host
|
||||
.default_input_device()
|
||||
@@ -267,9 +260,7 @@ pub fn start_recording(
|
||||
.build_input_stream(
|
||||
&mic_stream_config,
|
||||
move |data: &[f32], _| {
|
||||
let _ = tx_mic.send(AudioBuffer {
|
||||
data: data.to_vec(),
|
||||
});
|
||||
let _ = tx_mic.send(AudioBuffer { data: data.to_vec() });
|
||||
},
|
||||
|err| eprintln!("CPAL mic stream error: {err}"),
|
||||
None,
|
||||
@@ -282,9 +273,7 @@ pub fn start_recording(
|
||||
.build_input_stream(
|
||||
&lb_stream_config,
|
||||
move |data: &[f32], _| {
|
||||
let _ = tx_lb.send(AudioBuffer {
|
||||
data: data.to_vec(),
|
||||
});
|
||||
let _ = tx_lb.send(AudioBuffer { data: data.to_vec() });
|
||||
},
|
||||
|err| eprintln!("CPAL loopback stream error: {err}"),
|
||||
None,
|
||||
@@ -306,11 +295,7 @@ pub fn start_recording(
|
||||
let mono_samples: Vec<f32> = if mic_channels == 1 {
|
||||
buf.data
|
||||
} else {
|
||||
buf
|
||||
.data
|
||||
.chunks(mic_channels as usize)
|
||||
.map(to_mono)
|
||||
.collect()
|
||||
buf.data.chunks(mic_channels as usize).map(to_mono).collect()
|
||||
};
|
||||
pre_mic.extend_from_slice(&mono_samples);
|
||||
}
|
||||
@@ -347,10 +332,7 @@ pub fn start_recording(
|
||||
let lb_chunk: Vec<f32> = post_lb.drain(..TARGET_FRAME_SIZE).collect();
|
||||
let mixed = mix(&mic_chunk, &lb_chunk);
|
||||
if !mixed.is_empty() {
|
||||
let _ = audio_buffer_callback.call(
|
||||
Ok(mixed.clone().into()),
|
||||
ThreadsafeFunctionCallMode::NonBlocking,
|
||||
);
|
||||
let _ = audio_buffer_callback.call(Ok(mixed.clone().into()), ThreadsafeFunctionCallMode::NonBlocking);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,32 +3,31 @@ use std::{
|
||||
os::windows::ffi::OsStringExt,
|
||||
process,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
Arc, Mutex,
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
use napi::{
|
||||
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
|
||||
Result,
|
||||
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
|
||||
};
|
||||
use windows::{
|
||||
core::Interface,
|
||||
Win32::{
|
||||
Foundation::CloseHandle,
|
||||
Media::Audio::{
|
||||
eCapture, eCommunications, eConsole, AudioSessionState, AudioSessionStateActive,
|
||||
IAudioSessionControl, IAudioSessionControl2, IAudioSessionEnumerator, IAudioSessionEvents,
|
||||
IAudioSessionEvents_Impl, IAudioSessionManager2, IAudioSessionNotification,
|
||||
IAudioSessionNotification_Impl, IMMDevice, IMMDeviceCollection, IMMDeviceEnumerator,
|
||||
MMDeviceEnumerator, DEVICE_STATE_ACTIVE,
|
||||
AudioSessionState, AudioSessionStateActive, DEVICE_STATE_ACTIVE, IAudioSessionControl, IAudioSessionControl2,
|
||||
IAudioSessionEnumerator, IAudioSessionEvents, IAudioSessionEvents_Impl, IAudioSessionManager2,
|
||||
IAudioSessionNotification, IAudioSessionNotification_Impl, IMMDevice, IMMDeviceCollection, IMMDeviceEnumerator,
|
||||
MMDeviceEnumerator, eCapture, eCommunications, eConsole,
|
||||
},
|
||||
System::{
|
||||
Com::{CoCreateInstance, CoInitializeEx, CLSCTX_ALL, COINIT_MULTITHREADED},
|
||||
Com::{CLSCTX_ALL, COINIT_MULTITHREADED, CoCreateInstance, CoInitializeEx},
|
||||
ProcessStatus::{GetModuleFileNameExW, GetProcessImageFileNameW},
|
||||
Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ},
|
||||
},
|
||||
},
|
||||
core::Interface,
|
||||
};
|
||||
use windows_core::implement;
|
||||
|
||||
@@ -148,9 +147,7 @@ impl IAudioSessionEvents_Impl for SessionEvents_Impl {
|
||||
let currently_recording = newstate == AudioSessionStateActive;
|
||||
|
||||
// Atomically swap the flag tracking this particular session
|
||||
let previously_recording = self
|
||||
.session_is_active
|
||||
.swap(currently_recording, Ordering::SeqCst);
|
||||
let previously_recording = self.session_is_active.swap(currently_recording, Ordering::SeqCst);
|
||||
|
||||
// Update the global counter accordingly
|
||||
if !previously_recording && currently_recording {
|
||||
@@ -272,12 +269,7 @@ impl SessionNotifier {
|
||||
|
||||
if should_notify {
|
||||
self.callback.call(
|
||||
Ok((
|
||||
true,
|
||||
process_name,
|
||||
self.device_id.clone(),
|
||||
self.device_name.clone(),
|
||||
)),
|
||||
Ok((true, process_name, self.device_id.clone(), self.device_name.clone())),
|
||||
ThreadsafeFunctionCallMode::NonBlocking,
|
||||
);
|
||||
}
|
||||
@@ -309,8 +301,7 @@ pub fn register_audio_device_status_callback(
|
||||
let enumerator: IMMDeviceEnumerator = CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)?;
|
||||
|
||||
// Get all active capture devices
|
||||
let device_collection: IMMDeviceCollection =
|
||||
enumerator.EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE)?;
|
||||
let device_collection: IMMDeviceCollection = enumerator.EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE)?;
|
||||
|
||||
let device_count = device_collection.GetCount()?;
|
||||
let mut session_notifiers = Vec::new();
|
||||
@@ -374,17 +365,14 @@ impl MicrophoneListener {
|
||||
let is_running = Arc::new(AtomicBool::new(false));
|
||||
let active_sessions = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let session_notifiers = match register_audio_device_status_callback(
|
||||
is_running.clone(),
|
||||
active_sessions.clone(),
|
||||
Arc::new(callback),
|
||||
) {
|
||||
Ok(notifiers) => notifiers,
|
||||
Err(_) => {
|
||||
// If registration fails, create a listener with empty notifiers
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
let session_notifiers =
|
||||
match register_audio_device_status_callback(is_running.clone(), active_sessions.clone(), Arc::new(callback)) {
|
||||
Ok(notifiers) => notifiers,
|
||||
Err(_) => {
|
||||
// If registration fails, create a listener with empty notifiers
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
is_running,
|
||||
@@ -401,17 +389,13 @@ impl MicrophoneListener {
|
||||
pub fn is_process_using_microphone(process_id: u32) -> bool {
|
||||
// Use the proven get_all_audio_processes logic
|
||||
match get_all_audio_processes() {
|
||||
Ok(processes) => processes
|
||||
.iter()
|
||||
.any(|p| p.process_id == process_id && p.is_running),
|
||||
Ok(processes) => processes.iter().any(|p| p.process_id == process_id && p.is_running),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mgr_audio_session_running_status(
|
||||
mgr: &IAudioSessionManager2,
|
||||
) -> windows_core::Result<(bool, String)> {
|
||||
fn get_mgr_audio_session_running_status(mgr: &IAudioSessionManager2) -> windows_core::Result<(bool, String)> {
|
||||
let list: IAudioSessionEnumerator = unsafe { mgr.GetSessionEnumerator()? };
|
||||
let sessions = unsafe { list.GetCount()? };
|
||||
for idx in 0..sessions {
|
||||
@@ -445,8 +429,7 @@ fn get_mgr_audio_session_running_status(
|
||||
fn get_process_name(pid: u32) -> Option<String> {
|
||||
unsafe {
|
||||
// Open process with required access rights
|
||||
let process_handle =
|
||||
OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid).ok()?;
|
||||
let process_handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid).ok()?;
|
||||
|
||||
// Allocate a buffer large enough to hold extended-length paths (up to ~32K
|
||||
// characters) instead of the legacy MAX_PATH (260) limit.
|
||||
@@ -489,8 +472,8 @@ pub fn list_audio_processes() -> Result<Vec<AudioProcess>> {
|
||||
let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
|
||||
};
|
||||
|
||||
let result = get_all_audio_processes()
|
||||
.map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?;
|
||||
let result =
|
||||
get_all_audio_processes().map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -501,8 +484,7 @@ pub fn list_audio_devices() -> Result<Vec<AudioDevice>> {
|
||||
let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
|
||||
};
|
||||
|
||||
let result = get_all_audio_devices()
|
||||
.map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?;
|
||||
let result = get_all_audio_devices().map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -511,8 +493,7 @@ fn get_all_audio_processes() -> windows_core::Result<Vec<AudioProcess>> {
|
||||
unsafe {
|
||||
let enumerator: IMMDeviceEnumerator = CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)?;
|
||||
|
||||
let device_collection: IMMDeviceCollection =
|
||||
enumerator.EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE)?;
|
||||
let device_collection: IMMDeviceCollection = enumerator.EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE)?;
|
||||
|
||||
let device_count = device_collection.GetCount()?;
|
||||
let mut all_processes = Vec::new();
|
||||
@@ -569,8 +550,7 @@ fn get_all_audio_devices() -> windows_core::Result<Vec<AudioDevice>> {
|
||||
unsafe {
|
||||
let enumerator: IMMDeviceEnumerator = CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)?;
|
||||
|
||||
let device_collection: IMMDeviceCollection =
|
||||
enumerator.EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE)?;
|
||||
let device_collection: IMMDeviceCollection = enumerator.EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE)?;
|
||||
|
||||
let device_count = device_collection.GetCount()?;
|
||||
let mut devices = Vec::new();
|
||||
@@ -619,8 +599,8 @@ pub fn get_active_audio_processes() -> Result<Vec<AudioProcess>> {
|
||||
let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
|
||||
};
|
||||
|
||||
let result = get_all_audio_processes()
|
||||
.map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?;
|
||||
let result =
|
||||
get_all_audio_processes().map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?;
|
||||
|
||||
// Filter to only return active/running processes
|
||||
let active_processes = result.into_iter().filter(|p| p.is_running).collect();
|
||||
@@ -633,8 +613,8 @@ pub fn is_process_actively_using_microphone(pid: u32) -> Result<bool> {
|
||||
let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
|
||||
};
|
||||
|
||||
let result = get_all_audio_processes()
|
||||
.map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?;
|
||||
let result =
|
||||
get_all_audio_processes().map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?;
|
||||
|
||||
// Check if the PID exists in the list of active processes
|
||||
let is_active = result
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::{
|
||||
ffi::OsString,
|
||||
os::windows::ffi::OsStringExt,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU32, Ordering},
|
||||
Arc, LazyLock, RwLock,
|
||||
atomic::{AtomicBool, AtomicU32, Ordering},
|
||||
},
|
||||
thread,
|
||||
time::Duration,
|
||||
@@ -18,9 +18,9 @@ use napi_derive::napi;
|
||||
// Windows API imports
|
||||
use windows::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE}; // HWND removed
|
||||
use windows::Win32::System::{
|
||||
Com::{CoInitializeEx, COINIT_MULTITHREADED},
|
||||
Com::{COINIT_MULTITHREADED, CoInitializeEx},
|
||||
Diagnostics::ToolHelp::{
|
||||
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, TH32CS_SNAPPROCESS,
|
||||
CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW, TH32CS_SNAPPROCESS,
|
||||
},
|
||||
ProcessStatus::{GetModuleFileNameExW, GetProcessImageFileNameW},
|
||||
Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ},
|
||||
@@ -34,20 +34,17 @@ pub type AudioObjectID = u32;
|
||||
|
||||
// Global storage for running applications (Windows equivalent of macOS audio
|
||||
// process list)
|
||||
static RUNNING_APPLICATIONS: LazyLock<RwLock<Vec<u32>>> =
|
||||
LazyLock::new(|| RwLock::new(get_running_processes()));
|
||||
static RUNNING_APPLICATIONS: LazyLock<RwLock<Vec<u32>>> = LazyLock::new(|| RwLock::new(get_running_processes()));
|
||||
|
||||
// Simple counter for generating unique handles
|
||||
static NEXT_HANDLE: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
// Global storage for active watchers
|
||||
static ACTIVE_APP_WATCHERS: LazyLock<
|
||||
RwLock<Vec<(u32, u32, Arc<ThreadsafeFunction<(), ()>>, Arc<AtomicBool>)>>,
|
||||
> = LazyLock::new(|| RwLock::new(Vec::new()));
|
||||
static ACTIVE_APP_WATCHERS: LazyLock<RwLock<Vec<(u32, u32, Arc<ThreadsafeFunction<(), ()>>, Arc<AtomicBool>)>>> =
|
||||
LazyLock::new(|| RwLock::new(Vec::new()));
|
||||
|
||||
static ACTIVE_LIST_WATCHERS: LazyLock<
|
||||
RwLock<Vec<(u32, Arc<ThreadsafeFunction<(), ()>>, Arc<AtomicBool>)>>,
|
||||
> = LazyLock::new(|| RwLock::new(Vec::new()));
|
||||
static ACTIVE_LIST_WATCHERS: LazyLock<RwLock<Vec<(u32, Arc<ThreadsafeFunction<(), ()>>, Arc<AtomicBool>)>>> =
|
||||
LazyLock::new(|| RwLock::new(Vec::new()));
|
||||
|
||||
// Plain struct for efficient transmission via napi-rs
|
||||
#[napi]
|
||||
@@ -103,10 +100,7 @@ impl ApplicationListChangedSubscriber {
|
||||
#[napi]
|
||||
pub fn unsubscribe(&self) -> Result<()> {
|
||||
if let Ok(mut watchers) = ACTIVE_LIST_WATCHERS.write() {
|
||||
if let Some(pos) = watchers
|
||||
.iter()
|
||||
.position(|(handle, _, _)| *handle == self.handle)
|
||||
{
|
||||
if let Some(pos) = watchers.iter().position(|(handle, _, _)| *handle == self.handle) {
|
||||
let (_, _, should_stop) = &watchers[pos];
|
||||
should_stop.store(true, Ordering::Relaxed);
|
||||
watchers.remove(pos);
|
||||
@@ -132,10 +126,7 @@ impl ApplicationStateChangedSubscriber {
|
||||
#[napi]
|
||||
pub fn unsubscribe(&self) {
|
||||
if let Ok(mut watchers) = ACTIVE_APP_WATCHERS.write() {
|
||||
if let Some(pos) = watchers
|
||||
.iter()
|
||||
.position(|(handle, _, _, _)| *handle == self.handle)
|
||||
{
|
||||
if let Some(pos) = watchers.iter().position(|(handle, _, _, _)| *handle == self.handle) {
|
||||
let (_, _, _, should_stop) = &watchers[pos];
|
||||
should_stop.store(true, Ordering::Relaxed);
|
||||
watchers.remove(pos);
|
||||
@@ -152,9 +143,7 @@ pub struct ShareableContent {
|
||||
#[napi]
|
||||
impl ShareableContent {
|
||||
#[napi]
|
||||
pub fn on_application_list_changed(
|
||||
callback: ThreadsafeFunction<(), ()>,
|
||||
) -> Result<ApplicationListChangedSubscriber> {
|
||||
pub fn on_application_list_changed(callback: ThreadsafeFunction<(), ()>) -> Result<ApplicationListChangedSubscriber> {
|
||||
let handle = NEXT_HANDLE.fetch_add(1, Ordering::Relaxed);
|
||||
let callback_arc = Arc::new(callback);
|
||||
|
||||
@@ -189,25 +178,20 @@ impl ShareableContent {
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
unsafe {
|
||||
CoInitializeEx(None, COINIT_MULTITHREADED)
|
||||
.ok()
|
||||
.unwrap_or_else(|_| {
|
||||
// COM initialization failed, but we can't return an error from
|
||||
// constructor This is typically not fatal as COM might
|
||||
// already be initialized
|
||||
});
|
||||
CoInitializeEx(None, COINIT_MULTITHREADED).ok().unwrap_or_else(|_| {
|
||||
// COM initialization failed, but we can't return an error from
|
||||
// constructor This is typically not fatal as COM might
|
||||
// already be initialized
|
||||
});
|
||||
}
|
||||
Self {}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn applications() -> Result<Vec<ApplicationInfo>> {
|
||||
let processes = RUNNING_APPLICATIONS.read().map_err(|_| {
|
||||
Error::new(
|
||||
Status::GenericFailure,
|
||||
"Failed to read running applications",
|
||||
)
|
||||
})?;
|
||||
let processes = RUNNING_APPLICATIONS
|
||||
.read()
|
||||
.map_err(|_| Error::new(Status::GenericFailure, "Failed to read running applications"))?;
|
||||
|
||||
let mut apps = Vec::new();
|
||||
for &process_id in processes.iter() {
|
||||
@@ -326,8 +310,7 @@ fn is_process_running(process_id: u32) -> bool {
|
||||
|
||||
fn get_process_name(pid: u32) -> Option<String> {
|
||||
unsafe {
|
||||
let process_handle =
|
||||
OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid).ok()?;
|
||||
let process_handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid).ok()?;
|
||||
// Allocate a buffer large enough to hold extended-length paths (up to ~32K
|
||||
// characters) instead of the legacy MAX_PATH (260) limit. 32 768 is the
|
||||
// maximum length supported by the Win32 APIs when the path is prefixed
|
||||
@@ -352,8 +335,7 @@ fn get_process_name(pid: u32) -> Option<String> {
|
||||
|
||||
fn get_process_executable_path(pid: u32) -> Option<String> {
|
||||
unsafe {
|
||||
let process_handle =
|
||||
OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid).ok()?;
|
||||
let process_handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid).ok()?;
|
||||
// Use a buffer that can hold extended-length paths. See rationale above.
|
||||
let mut buffer: Vec<u16> = std::iter::repeat(0).take(32_768).collect();
|
||||
|
||||
@@ -372,11 +354,7 @@ fn get_process_executable_path(pid: u32) -> Option<String> {
|
||||
}
|
||||
|
||||
// Helper function to start monitoring a specific process
|
||||
fn start_process_monitoring(
|
||||
handle: u32,
|
||||
process_id: u32,
|
||||
callback: Arc<ThreadsafeFunction<(), ()>>,
|
||||
) {
|
||||
fn start_process_monitoring(handle: u32, process_id: u32, callback: Arc<ThreadsafeFunction<(), ()>>) {
|
||||
let should_stop = Arc::new(AtomicBool::new(false));
|
||||
let should_stop_clone = should_stop.clone();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
name = "affine_nbstore"
|
||||
version = "0.0.0"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use super::{error::Result, storage::SqliteDocStorage, Blob, ListedBlob, SetBlob};
|
||||
use super::{Blob, ListedBlob, SetBlob, error::Result, storage::SqliteDocStorage};
|
||||
|
||||
impl SqliteDocStorage {
|
||||
pub async fn get_blob(&self, key: String) -> Result<Option<Blob>> {
|
||||
@@ -60,8 +60,7 @@ impl SqliteDocStorage {
|
||||
pub async fn list_blobs(&self) -> Result<Vec<ListedBlob>> {
|
||||
let result = sqlx::query_as!(
|
||||
ListedBlob,
|
||||
"SELECT key, size, mime, created_at FROM blobs WHERE deleted_at IS NULL ORDER BY created_at \
|
||||
DESC;"
|
||||
"SELECT key, size, mime, created_at FROM blobs WHERE deleted_at IS NULL ORDER BY created_at DESC;"
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await?;
|
||||
@@ -102,18 +101,12 @@ mod tests {
|
||||
|
||||
assert!(result.is_some());
|
||||
|
||||
storage
|
||||
.delete_blob("test_".to_string(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
storage.delete_blob("test_".to_string(), false).await.unwrap();
|
||||
|
||||
let result = storage.get_blob("test".to_string()).await.unwrap();
|
||||
assert!(result.is_none());
|
||||
|
||||
storage
|
||||
.delete_blob("test_2".to_string(), true)
|
||||
.await
|
||||
.unwrap();
|
||||
storage.delete_blob("test_2".to_string(), true).await.unwrap();
|
||||
|
||||
let result = storage.get_blob("test".to_string()).await.unwrap();
|
||||
assert!(result.is_none());
|
||||
@@ -146,15 +139,9 @@ mod tests {
|
||||
vec!["test_1", "test_2", "test_3", "test_4"]
|
||||
);
|
||||
|
||||
storage
|
||||
.delete_blob("test_2".to_string(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
storage.delete_blob("test_2".to_string(), false).await.unwrap();
|
||||
|
||||
storage
|
||||
.delete_blob("test_3".to_string(), true)
|
||||
.await
|
||||
.unwrap();
|
||||
storage.delete_blob("test_3".to_string(), true).await.unwrap();
|
||||
|
||||
let query = sqlx::query("SELECT COUNT(*) as len FROM blobs;")
|
||||
.fetch_one(&storage.pool)
|
||||
@@ -186,10 +173,7 @@ mod tests {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
storage
|
||||
.delete_blob("test_2".to_string(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
storage.delete_blob("test_2".to_string(), false).await.unwrap();
|
||||
storage.release_blobs().await.unwrap();
|
||||
|
||||
let query = sqlx::query("SELECT COUNT(*) as len FROM blobs;")
|
||||
|
||||
@@ -25,11 +25,7 @@ impl SqliteDocStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_blob_uploaded_at(
|
||||
&self,
|
||||
peer: String,
|
||||
blob_id: String,
|
||||
) -> Result<Option<NaiveDateTime>> {
|
||||
pub async fn get_blob_uploaded_at(&self, peer: String, blob_id: String) -> Result<Option<NaiveDateTime>> {
|
||||
let result = sqlx::query_scalar!(
|
||||
"SELECT uploaded_at FROM peer_blob_sync WHERE peer = ? AND blob_id = ?",
|
||||
peer,
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::ops::Deref;
|
||||
use chrono::{DateTime, NaiveDateTime};
|
||||
use sqlx::{QueryBuilder, Row};
|
||||
|
||||
use super::{error::Result, storage::SqliteDocStorage, DocClock, DocRecord, DocUpdate};
|
||||
use super::{DocClock, DocRecord, DocUpdate, error::Result, storage::SqliteDocStorage};
|
||||
|
||||
struct Meta {
|
||||
space_id: String,
|
||||
@@ -65,11 +65,7 @@ impl SqliteDocStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn push_update<Update: AsRef<[u8]>>(
|
||||
&self,
|
||||
doc_id: String,
|
||||
update: Update,
|
||||
) -> Result<NaiveDateTime> {
|
||||
pub async fn push_update<Update: AsRef<[u8]>>(&self, doc_id: String, update: Update) -> Result<NaiveDateTime> {
|
||||
let mut timestamp = DateTime::from_timestamp_millis(chrono::Utc::now().timestamp_millis())
|
||||
.unwrap()
|
||||
.naive_utc();
|
||||
@@ -171,11 +167,7 @@ impl SqliteDocStorage {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn mark_updates_merged(
|
||||
&self,
|
||||
doc_id: String,
|
||||
updates: Vec<NaiveDateTime>,
|
||||
) -> Result<u32> {
|
||||
pub async fn mark_updates_merged(&self, doc_id: String, updates: Vec<NaiveDateTime>) -> Result<u32> {
|
||||
let mut qb = QueryBuilder::new("DELETE FROM updates");
|
||||
|
||||
qb.push(" WHERE doc_id = ");
|
||||
@@ -297,10 +289,7 @@ mod tests {
|
||||
let storage = get_storage().await;
|
||||
|
||||
storage.set_space_id("test".to_string()).await.unwrap();
|
||||
storage
|
||||
.push_update("test".to_string(), vec![0, 0])
|
||||
.await
|
||||
.unwrap();
|
||||
storage.push_update("test".to_string(), vec![0, 0]).await.unwrap();
|
||||
storage
|
||||
.set_doc_snapshot(DocRecord {
|
||||
doc_id: "test".to_string(),
|
||||
@@ -311,11 +300,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
storage
|
||||
.set_peer_pulled_remote_clock(
|
||||
"remote".to_string(),
|
||||
"test".to_string(),
|
||||
Utc::now().naive_utc(),
|
||||
)
|
||||
.set_peer_pulled_remote_clock("remote".to_string(), "test".to_string(), Utc::now().naive_utc())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -344,10 +329,7 @@ mod tests {
|
||||
|
||||
assert_eq!(updates.len(), 1);
|
||||
|
||||
let snapshot = storage
|
||||
.get_doc_snapshot("new_id".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
let snapshot = storage.get_doc_snapshot("new_id".to_string()).await.unwrap();
|
||||
|
||||
assert!(snapshot.is_some());
|
||||
}
|
||||
@@ -359,19 +341,13 @@ mod tests {
|
||||
let updates = vec![vec![0, 0], vec![0, 1], vec![1, 0], vec![1, 1]];
|
||||
|
||||
for update in updates.iter() {
|
||||
storage
|
||||
.push_update("test".to_string(), update)
|
||||
.await
|
||||
.unwrap();
|
||||
storage.push_update("test".to_string(), update).await.unwrap();
|
||||
}
|
||||
|
||||
let result = storage.get_doc_updates("test".to_string()).await.unwrap();
|
||||
|
||||
assert_eq!(result.len(), 4);
|
||||
assert_eq!(
|
||||
result.iter().map(|u| u.bin.to_vec()).collect::<Vec<_>>(),
|
||||
updates
|
||||
);
|
||||
assert_eq!(result.iter().map(|u| u.bin.to_vec()).collect::<Vec<_>>(), updates);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -439,10 +415,7 @@ mod tests {
|
||||
assert_eq!(clocks.len(), 0);
|
||||
|
||||
for i in 1..5u32 {
|
||||
storage
|
||||
.push_update(format!("test_{i}"), vec![0, 0])
|
||||
.await
|
||||
.unwrap();
|
||||
storage.push_update(format!("test_{i}"), vec![0, 0]).await.unwrap();
|
||||
}
|
||||
|
||||
let clocks = storage.get_doc_clocks(None).await.unwrap();
|
||||
@@ -453,10 +426,7 @@ mod tests {
|
||||
vec!["test_1", "test_2", "test_3", "test_4"]
|
||||
);
|
||||
|
||||
let clocks = storage
|
||||
.get_doc_clocks(Some(Utc::now().naive_utc()))
|
||||
.await
|
||||
.unwrap();
|
||||
let clocks = storage.get_doc_clocks(Some(Utc::now().naive_utc())).await.unwrap();
|
||||
|
||||
assert_eq!(clocks.len(), 0);
|
||||
|
||||
@@ -473,10 +443,7 @@ mod tests {
|
||||
let updates = [vec![0, 0], vec![0, 1], vec![1, 0], vec![1, 1]];
|
||||
|
||||
for update in updates.iter() {
|
||||
storage
|
||||
.push_update("test".to_string(), update)
|
||||
.await
|
||||
.unwrap();
|
||||
storage.push_update("test".to_string(), update).await.unwrap();
|
||||
}
|
||||
|
||||
let updates = storage.get_doc_updates("test".to_string()).await.unwrap();
|
||||
@@ -484,11 +451,7 @@ mod tests {
|
||||
let result = storage
|
||||
.mark_updates_merged(
|
||||
"test".to_string(),
|
||||
updates
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|u| u.timestamp)
|
||||
.collect::<Vec<_>>(),
|
||||
updates.iter().skip(1).map(|u| u.timestamp).collect::<Vec<_>>(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use chrono::NaiveDateTime;
|
||||
|
||||
use super::{error::Result, storage::SqliteDocStorage, DocClock};
|
||||
use super::{DocClock, error::Result, storage::SqliteDocStorage};
|
||||
|
||||
impl SqliteDocStorage {
|
||||
pub async fn get_peer_remote_clocks(&self, peer: String) -> Result<Vec<DocClock>> {
|
||||
@@ -15,11 +15,7 @@ impl SqliteDocStorage {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn get_peer_remote_clock(
|
||||
&self,
|
||||
peer: String,
|
||||
doc_id: String,
|
||||
) -> Result<Option<DocClock>> {
|
||||
pub async fn get_peer_remote_clock(&self, peer: String, doc_id: String) -> Result<Option<DocClock>> {
|
||||
let result = sqlx::query_as!(
|
||||
DocClock,
|
||||
"SELECT doc_id, remote_clock as timestamp FROM peer_clocks WHERE peer = ? AND doc_id = ?",
|
||||
@@ -32,12 +28,7 @@ impl SqliteDocStorage {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn set_peer_remote_clock(
|
||||
&self,
|
||||
peer: String,
|
||||
doc_id: String,
|
||||
clock: NaiveDateTime,
|
||||
) -> Result<()> {
|
||||
pub async fn set_peer_remote_clock(&self, peer: String, doc_id: String, clock: NaiveDateTime) -> Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO peer_clocks (peer, doc_id, remote_clock)
|
||||
@@ -66,11 +57,7 @@ impl SqliteDocStorage {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn get_peer_pulled_remote_clock(
|
||||
&self,
|
||||
peer: String,
|
||||
doc_id: String,
|
||||
) -> Result<Option<DocClock>> {
|
||||
pub async fn get_peer_pulled_remote_clock(&self, peer: String, doc_id: String) -> Result<Option<DocClock>> {
|
||||
let result = sqlx::query_as!(
|
||||
DocClock,
|
||||
r#"SELECT doc_id, pulled_remote_clock as timestamp FROM peer_clocks WHERE peer = ? AND doc_id = ?"#,
|
||||
@@ -83,12 +70,7 @@ impl SqliteDocStorage {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn set_peer_pulled_remote_clock(
|
||||
&self,
|
||||
peer: String,
|
||||
doc_id: String,
|
||||
clock: NaiveDateTime,
|
||||
) -> Result<()> {
|
||||
pub async fn set_peer_pulled_remote_clock(&self, peer: String, doc_id: String, clock: NaiveDateTime) -> Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO peer_clocks (peer, doc_id, pulled_remote_clock)
|
||||
@@ -117,11 +99,7 @@ impl SqliteDocStorage {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn get_peer_pushed_clock(
|
||||
&self,
|
||||
peer: String,
|
||||
doc_id: String,
|
||||
) -> Result<Option<DocClock>> {
|
||||
pub async fn get_peer_pushed_clock(&self, peer: String, doc_id: String) -> Result<Option<DocClock>> {
|
||||
let result = sqlx::query_as!(
|
||||
DocClock,
|
||||
"SELECT doc_id, pushed_clock as timestamp FROM peer_clocks WHERE peer = ? AND doc_id = ?",
|
||||
@@ -134,12 +112,7 @@ impl SqliteDocStorage {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn set_peer_pushed_clock(
|
||||
&self,
|
||||
peer: String,
|
||||
doc_id: String,
|
||||
clock: NaiveDateTime,
|
||||
) -> Result<()> {
|
||||
pub async fn set_peer_pushed_clock(&self, peer: String, doc_id: String, clock: NaiveDateTime) -> Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO peer_clocks (peer, doc_id, pushed_clock)
|
||||
@@ -157,9 +130,7 @@ impl SqliteDocStorage {
|
||||
}
|
||||
|
||||
pub async fn clear_clocks(&self) -> Result<()> {
|
||||
sqlx::query("DELETE FROM peer_clocks;")
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
sqlx::query("DELETE FROM peer_clocks;").execute(&self.pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use affine_common::doc_parser::{parse_doc_from_binary, BlockInfo, CrawlResult, ParseError};
|
||||
use affine_common::doc_parser::{BlockInfo, CrawlResult, ParseError, parse_doc_from_binary};
|
||||
use memory_indexer::{SearchHit, SnapshotData};
|
||||
use napi_derive::napi;
|
||||
use serde::Serialize;
|
||||
@@ -94,10 +94,7 @@ impl From<(u32, u32)> for NativeMatch {
|
||||
|
||||
impl SqliteDocStorage {
|
||||
pub async fn crawl_doc_data(&self, doc_id: &str) -> Result<NativeCrawlResult> {
|
||||
let doc_bin = self
|
||||
.load_doc_binary(doc_id)
|
||||
.await?
|
||||
.ok_or(ParseError::DocNotFound)?;
|
||||
let doc_bin = self.load_doc_binary(doc_id).await?.ok_or(ParseError::DocNotFound)?;
|
||||
|
||||
let result = parse_doc_from_binary(doc_bin, doc_id.to_string())?;
|
||||
Ok(result.into())
|
||||
@@ -113,8 +110,7 @@ impl SqliteDocStorage {
|
||||
|
||||
updates.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
|
||||
|
||||
let mut segments =
|
||||
Vec::with_capacity(snapshot.as_ref().map(|_| 1).unwrap_or(0) + updates.len());
|
||||
let mut segments = Vec::with_capacity(snapshot.as_ref().map(|_| 1).unwrap_or(0) + updates.len());
|
||||
if let Some(record) = snapshot {
|
||||
segments.push(record.bin.to_vec());
|
||||
}
|
||||
@@ -134,12 +130,10 @@ impl SqliteDocStorage {
|
||||
for row in snapshots {
|
||||
let index_name: String = row.get("index_name");
|
||||
let data: Vec<u8> = row.get("data");
|
||||
if let Ok(decompressed) = zstd::stream::decode_all(std::io::Cursor::new(&data)) {
|
||||
if let Ok((snapshot, _)) =
|
||||
bincode::serde::decode_from_slice::<SnapshotData, _>(&decompressed, config)
|
||||
{
|
||||
index.load_snapshot(&index_name, snapshot);
|
||||
}
|
||||
if let Ok(decompressed) = zstd::stream::decode_all(std::io::Cursor::new(&data))
|
||||
&& let Ok((snapshot, _)) = bincode::serde::decode_from_slice::<SnapshotData, _>(&decompressed, config)
|
||||
{
|
||||
index.load_snapshot(&index_name, snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,8 +150,8 @@ impl SqliteDocStorage {
|
||||
if let Some(data) = snapshot_data {
|
||||
let blob = bincode::serde::encode_to_vec(&data, bincode::config::standard())
|
||||
.map_err(|e| Error::Serialization(e.to_string()))?;
|
||||
let compressed = zstd::stream::encode_all(std::io::Cursor::new(&blob), 4)
|
||||
.map_err(|e| Error::Serialization(e.to_string()))?;
|
||||
let compressed =
|
||||
zstd::stream::encode_all(std::io::Cursor::new(&blob), 4).map_err(|e| Error::Serialization(e.to_string()))?;
|
||||
|
||||
let mut tx = self.pool.begin().await?;
|
||||
|
||||
@@ -201,13 +195,7 @@ impl SqliteDocStorage {
|
||||
memory_indexer::InMemoryIndex::snapshot_version()
|
||||
}
|
||||
|
||||
pub async fn fts_add(
|
||||
&self,
|
||||
index_name: &str,
|
||||
doc_id: &str,
|
||||
text: &str,
|
||||
index: bool,
|
||||
) -> Result<()> {
|
||||
pub async fn fts_add(&self, index_name: &str, doc_id: &str, text: &str, index: bool) -> Result<()> {
|
||||
let mut idx = self.index.write().await;
|
||||
idx.add_doc(index_name, doc_id, text, index);
|
||||
Ok(())
|
||||
@@ -226,21 +214,10 @@ impl SqliteDocStorage {
|
||||
|
||||
pub async fn fts_search(&self, index_name: &str, query: &str) -> Result<Vec<NativeSearchHit>> {
|
||||
let idx = self.index.read().await;
|
||||
Ok(
|
||||
idx
|
||||
.search_hits(index_name, query)
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
)
|
||||
Ok(idx.search_hits(index_name, query).into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
||||
pub async fn fts_get_matches(
|
||||
&self,
|
||||
index_name: &str,
|
||||
doc_id: &str,
|
||||
query: &str,
|
||||
) -> Result<Vec<NativeMatch>> {
|
||||
pub async fn fts_get_matches(&self, index_name: &str, doc_id: &str, query: &str) -> Result<Vec<NativeMatch>> {
|
||||
let idx = self.index.read().await;
|
||||
Ok(
|
||||
idx
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use chrono::NaiveDateTime;
|
||||
|
||||
use super::{error::Result, storage::SqliteDocStorage, DocIndexedClock};
|
||||
use super::{DocIndexedClock, error::Result, storage::SqliteDocStorage};
|
||||
|
||||
impl SqliteDocStorage {
|
||||
pub async fn get_doc_indexed_clock(&self, doc_id: String) -> Result<Option<DocIndexedClock>> {
|
||||
@@ -70,10 +70,7 @@ mod tests {
|
||||
let storage = get_storage().await;
|
||||
let ts = Utc::now().naive_utc();
|
||||
|
||||
storage
|
||||
.set_doc_indexed_clock("doc1".to_string(), ts, 1)
|
||||
.await
|
||||
.unwrap();
|
||||
storage.set_doc_indexed_clock("doc1".to_string(), ts, 1).await.unwrap();
|
||||
|
||||
let clock = storage
|
||||
.get_doc_indexed_clock("doc1".to_string())
|
||||
@@ -91,20 +88,11 @@ mod tests {
|
||||
let storage = get_storage().await;
|
||||
let ts = Utc::now().naive_utc();
|
||||
|
||||
storage
|
||||
.set_doc_indexed_clock("doc1".to_string(), ts, 1)
|
||||
.await
|
||||
.unwrap();
|
||||
storage.set_doc_indexed_clock("doc1".to_string(), ts, 1).await.unwrap();
|
||||
|
||||
storage
|
||||
.clear_doc_indexed_clock("doc1".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
storage.clear_doc_indexed_clock("doc1".to_string()).await.unwrap();
|
||||
|
||||
let clock = storage
|
||||
.get_doc_indexed_clock("doc1".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
let clock = storage.get_doc_indexed_clock("doc1".to_string()).await.unwrap();
|
||||
|
||||
assert!(clock.is_none());
|
||||
}
|
||||
|
||||
@@ -128,16 +128,8 @@ impl DocStoragePool {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn crawl_doc_data(
|
||||
&self,
|
||||
universal_id: String,
|
||||
doc_id: String,
|
||||
) -> Result<indexer::NativeCrawlResult> {
|
||||
let result = self
|
||||
.get(universal_id)
|
||||
.await?
|
||||
.crawl_doc_data(&doc_id)
|
||||
.await?;
|
||||
pub async fn crawl_doc_data(&self, universal_id: String, doc_id: String) -> Result<indexer::NativeCrawlResult> {
|
||||
let result = self.get(universal_id).await?.crawl_doc_data(&doc_id).await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
@@ -148,60 +140,23 @@ impl DocStoragePool {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn push_update(
|
||||
&self,
|
||||
universal_id: String,
|
||||
doc_id: String,
|
||||
update: Uint8Array,
|
||||
) -> Result<NaiveDateTime> {
|
||||
Ok(
|
||||
self
|
||||
.get(universal_id)
|
||||
.await?
|
||||
.push_update(doc_id, update)
|
||||
.await?,
|
||||
)
|
||||
pub async fn push_update(&self, universal_id: String, doc_id: String, update: Uint8Array) -> Result<NaiveDateTime> {
|
||||
Ok(self.get(universal_id).await?.push_update(doc_id, update).await?)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn get_doc_snapshot(
|
||||
&self,
|
||||
universal_id: String,
|
||||
doc_id: String,
|
||||
) -> Result<Option<DocRecord>> {
|
||||
Ok(
|
||||
self
|
||||
.get(universal_id)
|
||||
.await?
|
||||
.get_doc_snapshot(doc_id)
|
||||
.await?,
|
||||
)
|
||||
pub async fn get_doc_snapshot(&self, universal_id: String, doc_id: String) -> Result<Option<DocRecord>> {
|
||||
Ok(self.get(universal_id).await?.get_doc_snapshot(doc_id).await?)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn set_doc_snapshot(&self, universal_id: String, snapshot: DocRecord) -> Result<bool> {
|
||||
Ok(
|
||||
self
|
||||
.get(universal_id)
|
||||
.await?
|
||||
.set_doc_snapshot(snapshot)
|
||||
.await?,
|
||||
)
|
||||
Ok(self.get(universal_id).await?.set_doc_snapshot(snapshot).await?)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn get_doc_updates(
|
||||
&self,
|
||||
universal_id: String,
|
||||
doc_id: String,
|
||||
) -> Result<Vec<DocUpdate>> {
|
||||
Ok(
|
||||
self
|
||||
.get(universal_id)
|
||||
.await?
|
||||
.get_doc_updates(doc_id)
|
||||
.await?,
|
||||
)
|
||||
pub async fn get_doc_updates(&self, universal_id: String, doc_id: String) -> Result<Vec<DocUpdate>> {
|
||||
Ok(self.get(universal_id).await?.get_doc_updates(doc_id).await?)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -227,36 +182,18 @@ impl DocStoragePool {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn get_doc_clocks(
|
||||
&self,
|
||||
universal_id: String,
|
||||
after: Option<NaiveDateTime>,
|
||||
) -> Result<Vec<DocClock>> {
|
||||
pub async fn get_doc_clocks(&self, universal_id: String, after: Option<NaiveDateTime>) -> Result<Vec<DocClock>> {
|
||||
Ok(self.get(universal_id).await?.get_doc_clocks(after).await?)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn get_doc_clock(
|
||||
&self,
|
||||
universal_id: String,
|
||||
doc_id: String,
|
||||
) -> Result<Option<DocClock>> {
|
||||
pub async fn get_doc_clock(&self, universal_id: String, doc_id: String) -> Result<Option<DocClock>> {
|
||||
Ok(self.get(universal_id).await?.get_doc_clock(doc_id).await?)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn get_doc_indexed_clock(
|
||||
&self,
|
||||
universal_id: String,
|
||||
doc_id: String,
|
||||
) -> Result<Option<DocIndexedClock>> {
|
||||
Ok(
|
||||
self
|
||||
.get(universal_id)
|
||||
.await?
|
||||
.get_doc_indexed_clock(doc_id)
|
||||
.await?,
|
||||
)
|
||||
pub async fn get_doc_indexed_clock(&self, universal_id: String, doc_id: String) -> Result<Option<DocIndexedClock>> {
|
||||
Ok(self.get(universal_id).await?.get_doc_indexed_clock(doc_id).await?)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -277,11 +214,7 @@ impl DocStoragePool {
|
||||
|
||||
#[napi]
|
||||
pub async fn clear_doc_indexed_clock(&self, universal_id: String, doc_id: String) -> Result<()> {
|
||||
self
|
||||
.get(universal_id)
|
||||
.await?
|
||||
.clear_doc_indexed_clock(doc_id)
|
||||
.await?;
|
||||
self.get(universal_id).await?.clear_doc_indexed_clock(doc_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -297,17 +230,8 @@ impl DocStoragePool {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn delete_blob(
|
||||
&self,
|
||||
universal_id: String,
|
||||
key: String,
|
||||
permanently: bool,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.get(universal_id)
|
||||
.await?
|
||||
.delete_blob(key, permanently)
|
||||
.await?;
|
||||
pub async fn delete_blob(&self, universal_id: String, key: String, permanently: bool) -> Result<()> {
|
||||
self.get(universal_id).await?.delete_blob(key, permanently).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -323,18 +247,8 @@ impl DocStoragePool {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn get_peer_remote_clocks(
|
||||
&self,
|
||||
universal_id: String,
|
||||
peer: String,
|
||||
) -> Result<Vec<DocClock>> {
|
||||
Ok(
|
||||
self
|
||||
.get(universal_id)
|
||||
.await?
|
||||
.get_peer_remote_clocks(peer)
|
||||
.await?,
|
||||
)
|
||||
pub async fn get_peer_remote_clocks(&self, universal_id: String, peer: String) -> Result<Vec<DocClock>> {
|
||||
Ok(self.get(universal_id).await?.get_peer_remote_clocks(peer).await?)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -370,11 +284,7 @@ impl DocStoragePool {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn get_peer_pulled_remote_clocks(
|
||||
&self,
|
||||
universal_id: String,
|
||||
peer: String,
|
||||
) -> Result<Vec<DocClock>> {
|
||||
pub async fn get_peer_pulled_remote_clocks(&self, universal_id: String, peer: String) -> Result<Vec<DocClock>> {
|
||||
Ok(
|
||||
self
|
||||
.get(universal_id)
|
||||
@@ -417,18 +327,8 @@ impl DocStoragePool {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn get_peer_pushed_clocks(
|
||||
&self,
|
||||
universal_id: String,
|
||||
peer: String,
|
||||
) -> Result<Vec<DocClock>> {
|
||||
Ok(
|
||||
self
|
||||
.get(universal_id)
|
||||
.await?
|
||||
.get_peer_pushed_clocks(peer)
|
||||
.await?,
|
||||
)
|
||||
pub async fn get_peer_pushed_clocks(&self, universal_id: String, peer: String) -> Result<Vec<DocClock>> {
|
||||
Ok(self.get(universal_id).await?.get_peer_pushed_clocks(peer).await?)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -528,24 +428,14 @@ impl DocStoragePool {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn fts_delete_document(
|
||||
&self,
|
||||
id: String,
|
||||
index_name: String,
|
||||
doc_id: String,
|
||||
) -> Result<()> {
|
||||
pub async fn fts_delete_document(&self, id: String, index_name: String, doc_id: String) -> Result<()> {
|
||||
let storage = self.pool.get(id).await?;
|
||||
storage.fts_delete(&index_name, &doc_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn fts_get_document(
|
||||
&self,
|
||||
id: String,
|
||||
index_name: String,
|
||||
doc_id: String,
|
||||
) -> Result<Option<String>> {
|
||||
pub async fn fts_get_document(&self, id: String, index_name: String, doc_id: String) -> Result<Option<String>> {
|
||||
let storage = self.pool.get(id).await?;
|
||||
Ok(storage.fts_get(&index_name, &doc_id).await?)
|
||||
}
|
||||
@@ -570,11 +460,7 @@ impl DocStoragePool {
|
||||
query: String,
|
||||
) -> Result<Vec<indexer::NativeMatch>> {
|
||||
let storage = self.pool.get(id).await?;
|
||||
Ok(
|
||||
storage
|
||||
.fts_get_matches(&index_name, &doc_id, &query)
|
||||
.await?,
|
||||
)
|
||||
Ok(storage.fts_get_matches(&index_name, &doc_id, &query).await?)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,18 +44,12 @@ pub struct SqliteDocStoragePool {
|
||||
}
|
||||
|
||||
impl SqliteDocStoragePool {
|
||||
async fn get_or_create_storage<'a>(
|
||||
&'a self,
|
||||
universal_id: String,
|
||||
path: &str,
|
||||
) -> RefMut<'a, SqliteDocStorage> {
|
||||
let lock = RwLockWriteGuard::map(self.inner.write().await, |lock| {
|
||||
match lock.entry(universal_id) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
let storage = SqliteDocStorage::new(path.to_string());
|
||||
entry.insert(storage)
|
||||
}
|
||||
async fn get_or_create_storage<'a>(&'a self, universal_id: String, path: &str) -> RefMut<'a, SqliteDocStorage> {
|
||||
let lock = RwLockWriteGuard::map(self.inner.write().await, |lock| match lock.entry(universal_id) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
let storage = SqliteDocStorage::new(path.to_string());
|
||||
entry.insert(storage)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -79,9 +73,7 @@ impl SqliteDocStoragePool {
|
||||
|
||||
/// Initialize the database and run migrations.
|
||||
pub async fn connect(&self, universal_id: String, path: String) -> Result<()> {
|
||||
let storage = self
|
||||
.get_or_create_storage(universal_id.to_owned(), &path)
|
||||
.await;
|
||||
let storage = self.get_or_create_storage(universal_id.to_owned(), &path).await;
|
||||
|
||||
storage.connect().await?;
|
||||
Ok(())
|
||||
|
||||
@@ -3,9 +3,9 @@ use std::sync::Arc;
|
||||
use affine_schema::get_migrator;
|
||||
use memory_indexer::InMemoryIndex;
|
||||
use sqlx::{
|
||||
Pool, Row,
|
||||
migrate::MigrateDatabase,
|
||||
sqlite::{Sqlite, SqliteConnectOptions, SqlitePoolOptions},
|
||||
Pool, Row,
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
@@ -19,9 +19,7 @@ pub struct SqliteDocStorage {
|
||||
|
||||
impl SqliteDocStorage {
|
||||
pub fn new(path: String) -> Self {
|
||||
let sqlite_options = SqliteConnectOptions::new()
|
||||
.filename(&path)
|
||||
.foreign_keys(false);
|
||||
let sqlite_options = SqliteConnectOptions::new().filename(&path).foreign_keys(false);
|
||||
|
||||
let mut pool_options = SqlitePoolOptions::new();
|
||||
|
||||
@@ -94,9 +92,7 @@ impl SqliteDocStorage {
|
||||
/// Flush the WAL file to the database file.
|
||||
/// See https://www.sqlite.org/pragma.html#pragma_wal_checkpoint:~:text=PRAGMA%20schema.wal_checkpoint%3B
|
||||
pub async fn checkpoint(&self) -> Result<()> {
|
||||
sqlx::query("PRAGMA wal_checkpoint(FULL);")
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
sqlx::query("PRAGMA wal_checkpoint(FULL);").execute(&self.pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
name = "affine_schema"
|
||||
version = "0.0.0"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
name = "affine_sqlite_v1"
|
||||
version = "0.0.0"
|
||||
|
||||
|
||||
@@ -25,10 +25,7 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
sqlx::query(affine_schema::v1::SCHEMA)
|
||||
.execute(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query(affine_schema::v1::SCHEMA).execute(&pool).await.unwrap();
|
||||
|
||||
println!("cargo::rustc-env=DATABASE_URL=sqlite://{db_path}");
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ use chrono::NaiveDateTime;
|
||||
use napi::bindgen_prelude::{Buffer, Uint8Array};
|
||||
use napi_derive::napi;
|
||||
use sqlx::{
|
||||
Pool, Row,
|
||||
migrate::MigrateDatabase,
|
||||
sqlite::{Sqlite, SqliteConnectOptions, SqlitePoolOptions},
|
||||
Pool, Row,
|
||||
};
|
||||
|
||||
// latest version
|
||||
@@ -69,9 +69,7 @@ impl SqliteConnection {
|
||||
#[napi]
|
||||
pub async fn connect(&self) -> napi::Result<()> {
|
||||
if !Sqlite::database_exists(&self.path).await.unwrap_or(false) {
|
||||
Sqlite::create_database(&self.path)
|
||||
.await
|
||||
.map_err(anyhow::Error::from)?;
|
||||
Sqlite::create_database(&self.path).await.map_err(anyhow::Error::from)?;
|
||||
};
|
||||
let mut connection = self.pool.acquire().await.map_err(anyhow::Error::from)?;
|
||||
sqlx::query(affine_schema::v1::SCHEMA)
|
||||
@@ -89,8 +87,7 @@ impl SqliteConnection {
|
||||
let blob = blob.as_ref();
|
||||
sqlx::query_as!(
|
||||
BlobRow,
|
||||
"INSERT INTO blobs (key, data) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET data = \
|
||||
excluded.data",
|
||||
"INSERT INTO blobs (key, data) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET data = excluded.data",
|
||||
key,
|
||||
blob,
|
||||
)
|
||||
@@ -102,14 +99,10 @@ impl SqliteConnection {
|
||||
|
||||
#[napi]
|
||||
pub async fn get_blob(&self, key: String) -> Option<BlobRow> {
|
||||
sqlx::query_as!(
|
||||
BlobRow,
|
||||
"SELECT key, data, timestamp FROM blobs WHERE key = ?",
|
||||
key
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.ok()
|
||||
sqlx::query_as!(BlobRow, "SELECT key, data, timestamp FROM blobs WHERE key = ?", key)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -190,14 +183,11 @@ impl SqliteConnection {
|
||||
pub async fn get_updates_count(&self, doc_id: Option<String>) -> napi::Result<i64> {
|
||||
let count = match doc_id {
|
||||
Some(doc_id) => {
|
||||
sqlx::query!(
|
||||
"SELECT COUNT(*) as count FROM updates WHERE doc_id = ?",
|
||||
doc_id
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(anyhow::Error::from)?
|
||||
.count
|
||||
sqlx::query!("SELECT COUNT(*) as count FROM updates WHERE doc_id = ?", doc_id)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(anyhow::Error::from)?
|
||||
.count
|
||||
}
|
||||
None => {
|
||||
sqlx::query!("SELECT COUNT(*) as count FROM updates WHERE doc_id is NULL")
|
||||
@@ -239,11 +229,7 @@ impl SqliteConnection {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn replace_updates(
|
||||
&self,
|
||||
doc_id: Option<String>,
|
||||
updates: Vec<InsertRow>,
|
||||
) -> napi::Result<()> {
|
||||
pub async fn replace_updates(&self, doc_id: Option<String>, updates: Vec<InsertRow>) -> napi::Result<()> {
|
||||
let mut transaction = self.pool.begin().await.map_err(anyhow::Error::from)?;
|
||||
|
||||
match doc_id {
|
||||
@@ -289,8 +275,7 @@ impl SqliteConnection {
|
||||
pub async fn set_server_clock(&self, key: String, data: Uint8Array) -> napi::Result<()> {
|
||||
let data = data.as_ref();
|
||||
sqlx::query!(
|
||||
"INSERT INTO server_clock (key, data) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET data = \
|
||||
excluded.data",
|
||||
"INSERT INTO server_clock (key, data) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET data = excluded.data",
|
||||
key,
|
||||
data,
|
||||
)
|
||||
@@ -344,8 +329,7 @@ impl SqliteConnection {
|
||||
pub async fn set_sync_metadata(&self, key: String, data: Uint8Array) -> napi::Result<()> {
|
||||
let data = data.as_ref();
|
||||
sqlx::query!(
|
||||
"INSERT INTO sync_metadata (key, data) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET data \
|
||||
= excluded.data",
|
||||
"INSERT INTO sync_metadata (key, data) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET data = excluded.data",
|
||||
key,
|
||||
data,
|
||||
)
|
||||
@@ -439,11 +423,7 @@ impl SqliteConnection {
|
||||
|
||||
#[napi]
|
||||
pub async fn validate(path: String) -> ValidationResult {
|
||||
let pool = match SqlitePoolOptions::new()
|
||||
.max_connections(1)
|
||||
.connect(&path)
|
||||
.await
|
||||
{
|
||||
let pool = match SqlitePoolOptions::new().max_connections(1).connect(&path).await {
|
||||
Ok(pool) => pool,
|
||||
Err(_) => return ValidationResult::GeneralError,
|
||||
};
|
||||
@@ -472,9 +452,7 @@ impl SqliteConnection {
|
||||
Err(_) => return ValidationResult::GeneralError,
|
||||
};
|
||||
|
||||
let columns_res = sqlx::query("PRAGMA table_info(updates)")
|
||||
.fetch_all(&pool)
|
||||
.await;
|
||||
let columns_res = sqlx::query("PRAGMA table_info(updates)").fetch_all(&pool).await;
|
||||
|
||||
let doc_id_exist = match columns_res {
|
||||
Ok(res) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use affine_common::hashcash::Stamp;
|
||||
use napi::{bindgen_prelude::AsyncTask, Env, Result, Task};
|
||||
use napi::{Env, Result, Task, bindgen_prelude::AsyncTask};
|
||||
use napi_derive::napi;
|
||||
|
||||
pub struct AsyncVerifyChallengeResponse {
|
||||
@@ -61,9 +61,6 @@ impl Task for AsyncMintChallengeResponse {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn mint_challenge_response(
|
||||
resource: String,
|
||||
bits: Option<u32>,
|
||||
) -> AsyncTask<AsyncMintChallengeResponse> {
|
||||
pub fn mint_challenge_response(resource: String, bits: Option<u32>) -> AsyncTask<AsyncMintChallengeResponse> {
|
||||
AsyncTask::new(AsyncMintChallengeResponse { bits, resource })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user