<commit_before>//! Dynamic library facilities.
//!
//! A simple wrapper over the platform's dynamic library facilities

use std::ffi::CString;
use std::path::Path;

pub struct DynamicLibrary {
    handle: *mut u8,
}

impl Drop for DynamicLibrary {
    fn drop(&mut self) {
        unsafe { dl::close(self.handle) }
    }
}

impl DynamicLibrary {
    /// Lazily open a dynamic library.
    pub fn open(filename: &Path) -> Result<DynamicLibrary, String> {
        let maybe_library = dl::open(filename.as_os_str());

        // The dynamic library must not be constructed if there is
        // an error opening the library so the destructor does not
        // run.
        match maybe_library {
            Err(err) => Err(err),
            Ok(handle) => Ok(DynamicLibrary { handle }),
        }
    }

    /// Accesses the value at the symbol of the dynamic library.
    pub unsafe fn symbol<T>(&self, symbol: &str) -> Result<*mut T, String> {
        // This function should have a lifetime constraint of 'a on
        // T but that feature is still unimplemented

        let raw_string = CString::new(symbol).unwrap();
        let maybe_symbol_value = dl::symbol(self.handle, raw_string.as_ptr());

        // The value must not be constructed if there is an error so
        // the destructor does not run.
        match maybe_symbol_value {
            Err(err) => Err(err),
            Ok(symbol_value) => Ok(symbol_value as *mut T),
        }
    }
}

#[cfg(test)]
mod tests;

#[cfg(unix)]
mod dl {
    use std::ffi::{CString, OsStr};
    use std::os::unix::prelude::*;

    // `dlerror` is process global, so we can only allow a single thread at a
    // time to call `dlsym` and `dlopen` if we want to check the error message.
    mod error {
        use std::ffi::CStr;
        use std::lazy::SyncLazy;
        use std::sync::{Mutex, MutexGuard};

        pub fn lock() -> MutexGuard<'static, Guard> {
            static LOCK: SyncLazy<Mutex<Guard>> = SyncLazy::new(|| Mutex::new(Guard { _priv: () }));
            LOCK.lock().unwrap()
        }

        pub struct Guard {
            _priv: (),
        }

        impl Guard {
            pub fn get(&mut self) -> Result<(), String> {
                let msg = unsafe { libc::dlerror() };
                if msg.is_null() {
                    Ok(())
                } else {
                    let msg = unsafe { CStr::from_ptr(msg as *const _) };
                    Err(msg.to_string_lossy().into_owned())
                }
            }

            pub fn clear(&mut self) {
                let _ = unsafe { libc::dlerror() };
            }
        }
    }

    pub(super) fn open(filename: &OsStr) -> Result<*mut u8, String> {
        let s = CString::new(filename.as_bytes()).unwrap();

        let mut dlerror = error::lock();
        let ret = unsafe { libc::dlopen(s.as_ptr(), libc::RTLD_LAZY) } as *mut u8;

        if !ret.is_null() {
            return Ok(ret);
        }

        // A NULL return from `dlopen` indicates that an error has
        // definitely occurred, so if nothing is in `dlerror`, we are
        // racing with another thread that has stolen our error message.
        dlerror.get().and_then(|()| Err("Unknown error".to_string()))
    }

    pub(super) unsafe fn symbol(
        handle: *mut u8,
        symbol: *const libc::c_char,
    ) -> Result<*mut u8, String> {
        // HACK(#74469): On some platforms, users observed foreign code
        // (specifically libc) invoking `dlopen`/`dlsym` in parallel with the
        // functions in this module. This is problematic because, according to
        // the POSIX API documentation, `dlerror` must be called to determine
        // whether `dlsym` succeeded. Unlike `dlopen`, a NULL return value may
        // indicate a successfully resolved symbol with an address of zero.
        //
        // Because symbols with address zero shouldn't occur in practice, we
        // treat them as errors on platforms with misbehaving libc
        // implementations.
        const DLSYM_NULL_IS_ERROR: bool = cfg!(target_os = "illumos");

        let mut dlerror = error::lock();

        // No need to flush `dlerror` if we aren't using it to determine whether
        // the subsequent call to `dlsym` succeeded. If an error occurs, any
        // stale value will be overwritten.
        if !DLSYM_NULL_IS_ERROR {
            dlerror.clear();
        }

        let ret = libc::dlsym(handle as *mut libc::c_void, symbol) as *mut u8;

        // A non-NULL return value *always* indicates success. There's no need
        // to check `dlerror`.
        if !ret.is_null() {
            return Ok(ret);
        }

        match dlerror.get() {
            Ok(()) if DLSYM_NULL_IS_ERROR => Err("Unknown error".to_string()),
            Ok(()) => Ok(ret),

            Err(msg) => Err(msg),
        }
    }

    pub(super) unsafe fn close(handle: *mut u8) {
        libc::dlclose(handle as *mut libc::c_void);
    }
}

#[cfg(windows)]
mod dl {
    use std::ffi::OsStr;
    use std::io;
    use std::os::windows::prelude::*;
    use std::ptr;

    use winapi::shared::minwindef::HMODULE;
    use winapi::um::errhandlingapi::SetThreadErrorMode;
    use winapi::um::libloaderapi::{FreeLibrary, GetProcAddress, LoadLibraryW};
    use winapi::um::winbase::SEM_FAILCRITICALERRORS;

    pub(super) fn open(filename: &OsStr) -> Result<*mut u8, String> {
        // disable "dll load failed" error dialog.
        let prev_error_mode = unsafe {
            let new_error_mode = SEM_FAILCRITICALERRORS;
            let mut prev_error_mode = 0;
            let result = SetThreadErrorMode(new_error_mode, &mut prev_error_mode);
            if result == 0 {
                return Err(io::Error::last_os_error().to_string());
            }
            prev_error_mode
        };

        let filename_str: Vec<_> = filename.encode_wide().chain(Some(0)).collect();
        let result = unsafe { LoadLibraryW(filename_str.as_ptr()) } as *mut u8;
        let result = ptr_result(result);

        unsafe {
            SetThreadErrorMode(prev_error_mode, ptr::null_mut());
        }

        result
    }

    pub(super) unsafe fn symbol(
        handle: *mut u8,
        symbol: *const libc::c_char,
    ) -> Result<*mut u8, String> {
        let ptr = GetProcAddress(handle as HMODULE, symbol) as *mut u8;
        ptr_result(ptr)
    }

    pub(super) unsafe fn close(handle: *mut u8) {
        FreeLibrary(handle as HMODULE);
    }

    fn ptr_result<T>(ptr: *mut T) -> Result<*mut T, String> {
        if ptr.is_null() { Err(io::Error::last_os_error().to_string()) } else { Ok(ptr) }
    }
}
<commit_msg>Always treat `dlsym` returning NULL as an error<commit_after>//! Dynamic library facilities.
//!
//! A simple wrapper over the platform's dynamic library facilities

use std::ffi::CString;
use std::path::Path;

pub struct DynamicLibrary {
    handle: *mut u8,
}

impl Drop for DynamicLibrary {
    fn drop(&mut self) {
        unsafe { dl::close(self.handle) }
    }
}

impl DynamicLibrary {
    /// Lazily open a dynamic library.
    pub fn open(filename: &Path) -> Result<DynamicLibrary, String> {
        let maybe_library = dl::open(filename.as_os_str());

        // The dynamic library must not be constructed if there is
        // an error opening the library so the destructor does not
        // run.
        match maybe_library {
            Err(err) => Err(err),
            Ok(handle) => Ok(DynamicLibrary { handle }),
        }
    }

    /// Accesses the value at the symbol of the dynamic library.
    pub unsafe fn symbol<T>(&self, symbol: &str) -> Result<*mut T, String> {
        // This function should have a lifetime constraint of 'a on
        // T but that feature is still unimplemented

        let raw_string = CString::new(symbol).unwrap();
        let maybe_symbol_value = dl::symbol(self.handle, raw_string.as_ptr());

        // The value must not be constructed if there is an error so
        // the destructor does not run.
        match maybe_symbol_value {
            Err(err) => Err(err),
            Ok(symbol_value) => Ok(symbol_value as *mut T),
        }
    }
}

#[cfg(test)]
mod tests;

#[cfg(unix)]
mod dl {
    use std::ffi::{CString, OsStr};
    use std::os::unix::prelude::*;

    // As of the 2017 revision of the POSIX standard (IEEE 1003.1-2017), it is
    // implementation-defined whether `dlerror` is thread-safe (in which case it returns the most
    // recent error in the calling thread) or not thread-safe (in which case it returns the most
    // recent error in *any* thread).
    //
    // There's no easy way to tell what strategy is used by a given POSIX implementation, so we
    // lock around all calls that can modify `dlerror` in this module lest we accidentally read an
    // error from a different thread. This is bulletproof when we are the *only* code using the
    // dynamic library APIs at a given point in time. However, it's still possible for us to race
    // with other code (see #74469) on platforms where `dlerror` is not thread-safe.
    mod error {
        use std::ffi::CStr;
        use std::lazy::SyncLazy;
        use std::sync::{Mutex, MutexGuard};

        pub fn lock() -> MutexGuard<'static, Guard> {
            static LOCK: SyncLazy<Mutex<Guard>> = SyncLazy::new(|| Mutex::new(Guard { _priv: () }));
            LOCK.lock().unwrap()
        }

        pub struct Guard {
            _priv: (),
        }

        impl Guard {
            pub fn get(&mut self) -> Result<(), String> {
                let msg = unsafe { libc::dlerror() };
                if msg.is_null() {
                    Ok(())
                } else {
                    let msg = unsafe { CStr::from_ptr(msg as *const _) };
                    Err(msg.to_string_lossy().into_owned())
                }
            }

            pub fn clear(&mut self) {
                let _ = unsafe { libc::dlerror() };
            }
        }
    }

    pub(super) fn open(filename: &OsStr) -> Result<*mut u8, String> {
        let s = CString::new(filename.as_bytes()).unwrap();

        let mut dlerror = error::lock();
        let ret = unsafe { libc::dlopen(s.as_ptr(), libc::RTLD_LAZY) } as *mut u8;

        if !ret.is_null() {
            return Ok(ret);
        }

        // A NULL return from `dlopen` indicates that an error has definitely occurred, so if
        // nothing is in `dlerror`, we are racing with another thread that has stolen our error
        // message. See the explanation on the `dl::error` module for more information.
        dlerror.get().and_then(|()| Err("Unknown error".to_string()))
    }

    pub(super) unsafe fn symbol(
        handle: *mut u8,
        symbol: *const libc::c_char,
    ) -> Result<*mut u8, String> {
        let mut dlerror = error::lock();

        // Unlike `dlopen`, it's possible for `dlsym` to return NULL without overwriting `dlerror`.
        // Because of this, we clear `dlerror` before calling `dlsym` to avoid picking up a stale
        // error message by accident.
        dlerror.clear();

        let ret = libc::dlsym(handle as *mut libc::c_void, symbol) as *mut u8;

        if !ret.is_null() {
            return Ok(ret);
        }

        // If `dlsym` returns NULL but there is nothing in `dlerror` it means one of two things:
        // - We tried to load a symbol mapped to address 0. This is not technically an error but is
        //   unlikely to occur in practice and equally unlikely to be handled correctly by calling
        //   code. Therefore we treat it as an error anyway.
        // - An error has occurred, but we are racing with another thread that has stolen our error
        //   message. See the explanation on the `dl::error` module for more information.
        dlerror.get().and_then(|()| Err("Tried to load symbol mapped to address 0".to_string()))
    }

    pub(super) unsafe fn close(handle: *mut u8) {
        libc::dlclose(handle as *mut libc::c_void);
    }
}

#[cfg(windows)]
mod dl {
    use std::ffi::OsStr;
    use std::io;
    use std::os::windows::prelude::*;
    use std::ptr;

    use winapi::shared::minwindef::HMODULE;
    use winapi::um::errhandlingapi::SetThreadErrorMode;
    use winapi::um::libloaderapi::{FreeLibrary, GetProcAddress, LoadLibraryW};
    use winapi::um::winbase::SEM_FAILCRITICALERRORS;

    pub(super) fn open(filename: &OsStr) -> Result<*mut u8, String> {
        // disable "dll load failed" error dialog.
        let prev_error_mode = unsafe {
            let new_error_mode = SEM_FAILCRITICALERRORS;
            let mut prev_error_mode = 0;
            let result = SetThreadErrorMode(new_error_mode, &mut prev_error_mode);
            if result == 0 {
                return Err(io::Error::last_os_error().to_string());
            }
            prev_error_mode
        };

        let filename_str: Vec<_> = filename.encode_wide().chain(Some(0)).collect();
        let result = unsafe { LoadLibraryW(filename_str.as_ptr()) } as *mut u8;
        let result = ptr_result(result);

        unsafe {
            SetThreadErrorMode(prev_error_mode, ptr::null_mut());
        }

        result
    }

    pub(super) unsafe fn symbol(
        handle: *mut u8,
        symbol: *const libc::c_char,
    ) -> Result<*mut u8, String> {
        let ptr = GetProcAddress(handle as HMODULE, symbol) as *mut u8;
        ptr_result(ptr)
    }

    pub(super) unsafe fn close(handle: *mut u8) {
        FreeLibrary(handle as HMODULE);
    }

    fn ptr_result<T>(ptr: *mut T) -> Result<*mut T, String> {
        if ptr.is_null() { Err(io::Error::last_os_error().to_string()) } else { Ok(ptr) }
    }
}
<|endoftext|>


<commit_before>use std::cast;
use std::ptr;
use std::str::raw::from_c_str;
use std::task;

use gtk::*;
use gtk::ffi::*;

use gui;

struct Player {
    initialized: bool,

    playing: bool,

    playbin: *mut GstElement,
    clock_id: Option<GstClockID>,
}

impl Player {
    pub fn new() -> Player {
        Player {
            initialized: false,
            playing: false,
            playbin: ptr::mut_null(),
            clock_id: None,
        }
    }

    // It's important that the `gui` pointer be constant for the entire duration
    // of the program, as it's sent into the gstreamer lib.
    // I know, this is quite very hacky.
    pub fn init(&mut self, args: ~[~str], gui: &gui::Gui) -> ~[~str] {
        let args2 = unsafe {
            gst_init_with_args(args)
        };
        unsafe {
            "playbin".with_c_str(|c_str| {
                "rusttracks-playbin".with_c_str(|rtpb| {
                    self.playbin = gst_element_factory_make(c_str, rtpb);
                });
            });
            if self.playbin.is_null() {
                fail!("failed to create playbin");
            }

            let bus = gst_pipeline_get_bus(cast::transmute(self.playbin));
            gst_bus_add_watch(bus, bus_callback,
                              cast::transmute::<&gui::Gui, gpointer>(gui));
        }
        self.initialized = true;
        args2
    }

    pub fn set_uri(&mut self, uri: &str, gui: &gui::Gui) {
        self.stop();
        unsafe {
            "uri".with_c_str(|property_c_str| {
                uri.with_c_str(|uri_c_str| {
                    g_object_set(cast::transmute(self.playbin),
                        property_c_str, uri_c_str, ptr::null::<gchar>());
                });
            });

            let chan = gui.get_chan().clone();

            let clock = gst_pipeline_get_clock(cast::transmute(self.playbin));

            // in nanoseconds
            let timeout: guint64 = 30 * 1000 * 1000 * 1000;
            let target_time = gst_clock_get_time(clock) + timeout;

            let ci = gst_clock_new_single_shot_id(clock, target_time);
            self.clock_id = Some(ci);

            do task::spawn_sched(task::SingleThreaded) {
                let res = gst_clock_id_wait(ci, ptr::mut_null());
                match res {
                    GST_CLOCK_UNSCHEDULED => { } // Ignore, nothing to do
                    GST_CLOCK_OK => {
                        debug!("30s are up! sending ReportCurrentTrack to gui");
                        chan.send(gui::ReportCurrentTrack);
                    }
                    _ => unreachable!()
                }
            }

            gst_object_unref(cast::transmute(clock));
        }
    }

    pub fn play(&mut self) {
        if !self.initialized {
            fail!("player is not initialized");
        }
        unsafe {
            gst_element_set_state(self.playbin, GST_STATE_PLAYING);
        }
        self.playing = true;
    }

    pub fn pause(&mut self) {
        if !self.initialized {
            fail!("player is not initialized");
        }
        unsafe {
            gst_element_set_state(self.playbin, GST_STATE_PAUSED);
        }
        self.playing = false;
    }

    pub fn stop(&mut self) {
        if !self.initialized {
            fail!("player is not initialized");
        }
        unsafe {
            let maybe_ci = self.clock_id.take();
            for ci in maybe_ci.move_iter() {
                gst_clock_id_unschedule(ci);
                gst_clock_id_unref(ci);
            }
            gst_element_set_state(self.playbin, GST_STATE_READY);
        }
        self.playing = false;
    }

    pub fn toggle(&mut self) {
        if self.playing {
            self.pause()
        } else {
            self.play()
        }
    }
}

impl Drop for Player {
    fn drop(&mut self) {
        if self.initialized {
            unsafe {
                if !self.playbin.is_null() {
                    gst_element_set_state(self.playbin, GST_STATE_NULL);
                    gst_object_unref(cast::transmute(self.playbin));
                }
                gst_deinit();
            }
        }
    }
}

extern "C" fn bus_callback(_bus: *mut GstBus, msg: *mut GstMessage, data: gpointer) -> gboolean {
    unsafe {
    let gui: &gui::Gui = cast::transmute(data);

    let name = {
        let gst_obj = (*msg).src;
        if gst_obj.is_null() {
            ~"null-source"
        } else {
            let name_ptr = gst_object_get_name(gst_obj);
            if name_ptr.is_null() {
                ~"null-name"
            } else {
                let name = from_c_str(cast::transmute_immut_unsafe(name_ptr));
                g_free(cast::transmute(name_ptr));
                name
            }
        }
    };

    match (*msg)._type {
        GST_MESSAGE_ERROR => {
            let mut err = ptr::mut_null();
            let mut dbg_info = ptr::mut_null();

            gst_message_parse_error(msg, &mut err, &mut dbg_info);

            error!("ERROR from element {}: {}", name,
                from_c_str(cast::transmute_immut_unsafe((*err).message)));
            error!("Debugging info: {}", from_c_str(cast::transmute_immut_unsafe(dbg_info)));

            g_error_free(err);
            g_free(cast::transmute(dbg_info));
        }
        GST_MESSAGE_WARNING => {
            if warn_enabled!() {
                let mut err = ptr::mut_null();
                let mut dbg_info = ptr::mut_null();

                gst_message_parse_error(msg, &mut err, &mut dbg_info);

                warn!("WARNING from element {}: {}", name,
                    from_c_str(cast::transmute_immut_unsafe((*err).message)));
                warn!("Debugging info: {}", from_c_str(cast::transmute_immut_unsafe(dbg_info)));

                g_error_free(err);
                g_free(cast::transmute(dbg_info));
            }
        }
        GST_MESSAGE_INFO => {
            if info_enabled!() {
                let mut err = ptr::mut_null();
                let mut dbg_info = ptr::mut_null();

                gst_message_parse_error(msg, &mut err, &mut dbg_info);

                info!("INFO from element {}: {}", name,
                    from_c_str(cast::transmute_immut_unsafe((*err).message)));
                info!("Debugging info: {}", from_c_str(cast::transmute_immut_unsafe(dbg_info)));

                g_error_free(err);
                g_free(cast::transmute(dbg_info));
            }
        }
        GST_MESSAGE_EOS => {
            debug!("EOS from element {}", name);
            gui.get_chan().send(gui::NextTrack);
        }
        _ => {
            if debug_enabled!() {
                let msg_type_cstr = gst_message_type_get_name((*msg)._type);
                let msg_type_name = ::std::str::raw::from_c_str(msg_type_cstr);
                debug!("message of type `{}` from element `{}`", msg_type_name, name);
            }
        }
    }

    // Returning 0 removes this callback
    return 1;
    }
}
<commit_msg>Update for language changes<commit_after>use std::cast;
use std::logging;
use std::ptr;
use std::str::raw::from_c_str;
use std::task;

use gtk::*;
use gtk::ffi::*;

use gui;

struct Player {
    initialized: bool,

    playing: bool,

    playbin: *mut GstElement,
    clock_id: Option<GstClockID>,
}

impl Player {
    pub fn new() -> Player {
        Player {
            initialized: false,
            playing: false,
            playbin: ptr::mut_null(),
            clock_id: None,
        }
    }

    // It's important that the `gui` pointer be constant for the entire duration
    // of the program, as it's sent into the gstreamer lib.
    // I know, this is quite very hacky.
    pub fn init(&mut self, args: ~[~str], gui: &gui::Gui) -> ~[~str] {
        let args2 = unsafe {
            gst_init_with_args(args)
        };
        unsafe {
            "playbin".with_c_str(|c_str| {
                "rusttracks-playbin".with_c_str(|rtpb| {
                    self.playbin = gst_element_factory_make(c_str, rtpb);
                });
            });
            if self.playbin.is_null() {
                fail!("failed to create playbin");
            }

            let bus = gst_pipeline_get_bus(cast::transmute(self.playbin));
            gst_bus_add_watch(bus, bus_callback,
                              cast::transmute::<&gui::Gui, gpointer>(gui));
        }
        self.initialized = true;
        args2
    }

    pub fn set_uri(&mut self, uri: &str, gui: &gui::Gui) {
        self.stop();
        unsafe {
            "uri".with_c_str(|property_c_str| {
                uri.with_c_str(|uri_c_str| {
                    g_object_set(cast::transmute(self.playbin),
                        property_c_str, uri_c_str, ptr::null::<gchar>());
                });
            });

            let chan = gui.get_chan().clone();

            let clock = gst_pipeline_get_clock(cast::transmute(self.playbin));

            // in nanoseconds
            let timeout: guint64 = 30 * 1000 * 1000 * 1000;
            let target_time = gst_clock_get_time(clock) + timeout;

            let ci = gst_clock_new_single_shot_id(clock, target_time);
            self.clock_id = Some(ci);

            do task::spawn_sched(task::SingleThreaded) {
                let res = gst_clock_id_wait(ci, ptr::mut_null());
                match res {
                    GST_CLOCK_UNSCHEDULED => { } // Ignore, nothing to do
                    GST_CLOCK_OK => {
                        debug!("30s are up! sending ReportCurrentTrack to gui");
                        chan.send(gui::ReportCurrentTrack);
                    }
                    _ => unreachable!()
                }
            }

            gst_object_unref(cast::transmute(clock));
        }
    }

    pub fn play(&mut self) {
        if !self.initialized {
            fail!("player is not initialized");
        }
        unsafe {
            gst_element_set_state(self.playbin, GST_STATE_PLAYING);
        }
        self.playing = true;
    }

    pub fn pause(&mut self) {
        if !self.initialized {
            fail!("player is not initialized");
        }
        unsafe {
            gst_element_set_state(self.playbin, GST_STATE_PAUSED);
        }
        self.playing = false;
    }

    pub fn stop(&mut self) {
        if !self.initialized {
            fail!("player is not initialized");
        }
        unsafe {
            let maybe_ci = self.clock_id.take();
            for ci in maybe_ci.move_iter() {
                gst_clock_id_unschedule(ci);
                gst_clock_id_unref(ci);
            }
            gst_element_set_state(self.playbin, GST_STATE_READY);
        }
        self.playing = false;
    }

    pub fn toggle(&mut self) {
        if self.playing {
            self.pause()
        } else {
            self.play()
        }
    }
}

impl Drop for Player {
    fn drop(&mut self) {
        if self.initialized {
            unsafe {
                if !self.playbin.is_null() {
                    gst_element_set_state(self.playbin, GST_STATE_NULL);
                    gst_object_unref(cast::transmute(self.playbin));
                }
                gst_deinit();
            }
        }
    }
}

extern "C" fn bus_callback(_bus: *mut GstBus, msg: *mut GstMessage, data: gpointer) -> gboolean {
    unsafe {
    let gui: &gui::Gui = cast::transmute(data);

    let name = {
        let gst_obj = (*msg).src;
        if gst_obj.is_null() {
            ~"null-source"
        } else {
            let name_ptr = gst_object_get_name(gst_obj);
            if name_ptr.is_null() {
                ~"null-name"
            } else {
                let name = from_c_str(cast::transmute_immut_unsafe(name_ptr));
                g_free(cast::transmute(name_ptr));
                name
            }
        }
    };

    match (*msg)._type {
        GST_MESSAGE_ERROR => {
            let mut err = ptr::mut_null();
            let mut dbg_info = ptr::mut_null();

            gst_message_parse_error(msg, &mut err, &mut dbg_info);

            error!("ERROR from element {}: {}", name,
                from_c_str(cast::transmute_immut_unsafe((*err).message)));
            error!("Debugging info: {}", from_c_str(cast::transmute_immut_unsafe(dbg_info)));

            g_error_free(err);
            g_free(cast::transmute(dbg_info));
        }
        GST_MESSAGE_WARNING => {
            if log_enabled!(logging::WARN) {
                let mut err = ptr::mut_null();
                let mut dbg_info = ptr::mut_null();

                gst_message_parse_error(msg, &mut err, &mut dbg_info);

                warn!("WARNING from element {}: {}", name,
                    from_c_str(cast::transmute_immut_unsafe((*err).message)));
                warn!("Debugging info: {}", from_c_str(cast::transmute_immut_unsafe(dbg_info)));

                g_error_free(err);
                g_free(cast::transmute(dbg_info));
            }
        }
        GST_MESSAGE_INFO => {
            if log_enabled!(logging::INFO) {
                let mut err = ptr::mut_null();
                let mut dbg_info = ptr::mut_null();

                gst_message_parse_error(msg, &mut err, &mut dbg_info);

                info!("INFO from element {}: {}", name,
                    from_c_str(cast::transmute_immut_unsafe((*err).message)));
                info!("Debugging info: {}", from_c_str(cast::transmute_immut_unsafe(dbg_info)));

                g_error_free(err);
                g_free(cast::transmute(dbg_info));
            }
        }
        GST_MESSAGE_EOS => {
            debug!("EOS from element {}", name);
            gui.get_chan().send(gui::NextTrack);
        }
        _ => {
            if log_enabled!(logging::DEBUG) {
                let msg_type_cstr = gst_message_type_get_name((*msg)._type);
                let msg_type_name = ::std::str::raw::from_c_str(msg_type_cstr);
                debug!("message of type `{}` from element `{}`", msg_type_name, name);
            }
        }
    }

    // Returning 0 removes this callback
    return 1;
    }
}
<|endoftext|>


<commit_before>#![allow(dead_code)]
use std::collections::HashMap;

type DocId = i32;
const MIN_DOC_ID: DocId = std::i32::MIN;
const MAX_DOC_ID: DocId = std::i32::MAX;

#[derive(Clone, Debug, PartialEq)]
struct ScoredDoc {
    id: DocId,
    score: f32,
}

struct Term<'a> {
    field: &'a str,
    token: &'a str,
    weight: f32,
}

type DensePostingList = Vec<ScoredDoc>;

#[derive(Debug)]
struct InvertedIndexNaive<'a> {
    posting_lists: HashMap<&'a str, HashMap<&'a str, DensePostingList>>,
}

enum AndState {
    Searching,
    HitEmptyList(usize),
    Done,
}

struct And<'a> {
    state: AndState,
    frontier: Vec<std::slice::Iter<'a, ScoredDoc>>,
    weights: Vec<f32>,
    last_viable_id: i32,
}

impl<'a> And<'a> {
    fn new(index: &'a InvertedIndexNaive, terms: &[Term]) -> Self {
        let mut frontier = Vec::with_capacity(terms.len());
        let mut weights = Vec::with_capacity(terms.len());
        // check to see if we can even find posing lists for all the terms
        for term in terms.iter() {
            let lookup = index
                .posting_lists
                .get(term.field)
                .and_then(|field_result| field_result.get(term.token));

            match lookup {
                None => {
                    return And {
                        state: AndState::Done,
                        frontier: Vec::new(),
                        weights: Vec::new(),
                        last_viable_id: MAX_DOC_ID,
                    }
                }
                Some(list) => {
                    frontier.push(list.iter());
                    weights.push(term.weight);
                }
            }
        }
        And {
            state: AndState::Searching,
            frontier,
            weights,
            last_viable_id: MIN_DOC_ID,
        }
    }
}

impl Iterator for And<'_> {
    type Item = ScoredDoc;

    fn next(&mut self) -> Option<ScoredDoc> {
        let mut matched_doc_count = 0;
        let mut score = 0.0;

        for posting_list_number in (0..self.frontier.len()).cycle() {
            let mut last_viable_id = self.last_viable_id;
            let list_iter = self.frontier.get_mut(posting_list_number).unwrap();
            let mut list_iter = list_iter.skip_while(|doc| doc.id < last_viable_id);
            match list_iter.next() {
                None => match self.state {
                    AndState::Searching => self.state = AndState::HitEmptyList(posting_list_number),
                    AndState::HitEmptyList(i) => {
                        if posting_list_number == i {
                            self.state = AndState::Done;
                        }
                    }
                    AndState::Done => break,
                },
                Some(doc) => {
                    if doc.id == last_viable_id {
                        score += self.weights[posting_list_number] * doc.score;
                        matched_doc_count += 1;
                    } else {
                        matched_doc_count = 0;
                        score = self.weights[posting_list_number] * doc.score;
                        last_viable_id = doc.id;
                    }
                }
            }
            if matched_doc_count == self.frontier.len() - 1 {
                return Some(ScoredDoc {
                    id: last_viable_id,
                    score,
                });
            }
            self.last_viable_id = last_viable_id;
        }
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn empty_and() {
        let inverted_index = &InvertedIndexNaive {
            posting_lists: HashMap::new(),
        };
        let term = &vec![Term {
            field: "your",
            token: "mom",
            weight: 9000.1,
        }];
        let and = And::new(inverted_index, term);
        assert_eq!(and.count(), 0);
    }

    #[test]
    fn nonempty_and() {
        let description_list: HashMap<&str, DensePostingList> = [
            (
                "very",
                vec![
                    ScoredDoc { id: 1, score: 1.0 },
                    ScoredDoc { id: 5, score: 1.0 },
                ],
            ),
            (
                "human",
                vec![
                    ScoredDoc { id: 2, score: 1.0 },
                    ScoredDoc { id: 5, score: 2.0 },
                ],
            ),
            (
                "like",
                vec![
                    ScoredDoc { id: 3, score: 1.0 },
                    ScoredDoc { id: 5, score: 3.0 },
                ],
            ),
            (
                "eyes",
                vec![
                    ScoredDoc { id: 4, score: 1.0 },
                    ScoredDoc { id: 5, score: 4.0 },
                ],
            ),
        ]
        .iter()
        .cloned()
        .collect();

        let title_list: HashMap<&str, DensePostingList> = [
            (
                "manul",
                vec![
                    ScoredDoc { id: 1, score: 1.0 },
                    ScoredDoc { id: 5, score: 5.0 },
                ],
            ),
            (
                "cat",
                vec![
                    ScoredDoc { id: 2, score: 1.0 },
                    ScoredDoc { id: 5, score: 6.0 },
                ],
            ),
            (
                "facial",
                vec![
                    ScoredDoc { id: 3, score: 1.0 },
                    ScoredDoc { id: 5, score: 7.0 },
                ],
            ),
            (
                "expression",
                vec![
                    ScoredDoc { id: 4, score: 1.0 },
                    ScoredDoc { id: 5, score: 8.0 },
                ],
            ),
        ]
        .iter()
        .cloned()
        .collect();

        let posting_lists = [("title", title_list), ("description", description_list)]
            .iter()
            .cloned()
            .collect();
        let inverted_index = InvertedIndexNaive {
            posting_lists,
        };

        let terms = vec![
            Term {
                field: "description",
                token: "very",
                weight: 1.0,
            },
            Term {
                field: "description",
                token: "human",
                weight: 10.0,
            },
            Term {
                field: "description",
                token: "like",
                weight: 100.0,
            },
            Term {
                field: "description",
                token: "eyes",
                weight: 1_000.0,
            },
            Term {
                field: "title",
                token: "manul",
                weight: 10_000.0,
            },
            Term {
                field: "title",
                token: "cat",
                weight: 100_000.0,
            },
            Term {
                field: "title",
                token: "facial",
                weight: 1_000_000.0,
            },
            Term {
                field: "title",
                token: "expression",
                weight: 10_000_000.0,
            },
        ];

        let and = And::new(&inverted_index, &terms);
        let actual: Vec<ScoredDoc> = and.collect();
        assert_eq!(
            actual,
            vec![ScoredDoc {
                id: 5,
                score: 87_654_321.0
            }]
        )
    }
}
<commit_msg>index creation convenience<commit_after>#![allow(dead_code)]
use std::collections::{BTreeMap, HashMap};
use std::iter::FromIterator;

type DocId = i32;
const MIN_DOC_ID: DocId = std::i32::MIN;
const MAX_DOC_ID: DocId = std::i32::MAX;

#[derive(Clone, Debug, PartialEq)]
struct ScoredDoc {
    id: DocId,
    score: f32,
}

struct Term<'a> {
    field: &'a str,
    token: &'a str,
    weight: f32,
}

// used to populate an index in a simple way
type IndexEntry<'a> = (&'a str, &'a str, DocId, f32);

type DensePostingList = Vec<ScoredDoc>;

#[derive(Debug)]
struct InvertedIndexNaive<'a> {
    posting_lists: HashMap<&'a str, HashMap<&'a str, DensePostingList>>,
}

impl<'a> FromIterator<IndexEntry<'a>> for InvertedIndexNaive<'a> {
    fn from_iter<T: IntoIterator<Item = IndexEntry<'a>>>(iter: T) -> Self {
        // insert field -> (token -> (doc id -> score))
        let mut nested_maps = HashMap::new();
        for (field, token, doc_id, payload) in iter {
            let field_entry = nested_maps.entry(field).or_insert(HashMap::new());
            let token_entry = field_entry.entry(token).or_insert(BTreeMap::new());
            token_entry.insert(doc_id, payload);
        }
        let mut posting_lists = HashMap::new();
        // turn BTreeMaps into DesnPostingLists
        for (field, token_map) in nested_maps {
            let field_entry = posting_lists.entry(field).or_insert(HashMap::new());
            for (token, score_map) in token_map {
                let posting_list = score_map
                    .iter()
                    .map(|x| ScoredDoc {
                        id: *x.0,
                        score: *x.1,
                    })
                    .collect();
                field_entry.insert(token, posting_list);
            }
        }
        return InvertedIndexNaive { posting_lists };
    }
}

enum AndState {
    Searching,
    HitEmptyList(usize),
    Done,
}

struct And<'a> {
    state: AndState,
    frontier: Vec<std::slice::Iter<'a, ScoredDoc>>,
    weights: Vec<f32>,
    last_viable_id: i32,
}

impl<'a> And<'a> {
    fn new(index: &'a InvertedIndexNaive, terms: &[Term]) -> Self {
        let mut frontier = Vec::with_capacity(terms.len());
        let mut weights = Vec::with_capacity(terms.len());
        // check to see if we can even find posing lists for all the terms
        for term in terms.iter() {
            let lookup = index
                .posting_lists
                .get(term.field)
                .and_then(|field_result| field_result.get(term.token));

            match lookup {
                None => {
                    return And {
                        state: AndState::Done,
                        frontier: Vec::new(),
                        weights: Vec::new(),
                        last_viable_id: MAX_DOC_ID,
                    }
                }
                Some(list) => {
                    frontier.push(list.iter());
                    weights.push(term.weight);
                }
            }
        }
        And {
            state: AndState::Searching,
            frontier,
            weights,
            last_viable_id: MIN_DOC_ID,
        }
    }
}

impl Iterator for And<'_> {
    type Item = ScoredDoc;

    fn next(&mut self) -> Option<ScoredDoc> {
        let mut matched_doc_count = 0;
        let mut score = 0.0;

        for posting_list_number in (0..self.frontier.len()).cycle() {
            let mut last_viable_id = self.last_viable_id;
            let list_iter = self.frontier.get_mut(posting_list_number).unwrap();
            let mut list_iter = list_iter.skip_while(|doc| doc.id < last_viable_id);
            match list_iter.next() {
                None => match self.state {
                    AndState::Searching => self.state = AndState::HitEmptyList(posting_list_number),
                    AndState::HitEmptyList(i) => {
                        if posting_list_number == i {
                            self.state = AndState::Done;
                        }
                    }
                    AndState::Done => break,
                },
                Some(doc) => {
                    if doc.id == last_viable_id {
                        score += self.weights[posting_list_number] * doc.score;
                        matched_doc_count += 1;
                    } else {
                        matched_doc_count = 0;
                        score = self.weights[posting_list_number] * doc.score;
                        last_viable_id = doc.id;
                    }
                }
            }
            if matched_doc_count == self.frontier.len() - 1 {
                return Some(ScoredDoc {
                    id: last_viable_id,
                    score,
                });
            }
            self.last_viable_id = last_viable_id;
        }
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn empty_and() {
        let inverted_index = &InvertedIndexNaive {
            posting_lists: HashMap::new(),
        };
        let term = &vec![Term {
            field: "your",
            token: "mom",
            weight: 9000.1,
        }];
        let and = And::new(inverted_index, term);
        assert_eq!(and.count(), 0);
    }

    #[test]
    fn nonempty_and() {
        let inverted_index: InvertedIndexNaive = [
            ("description", "very", 1, 1.0),
            ("description", "very", 5, 1.0),
            ("description", "human", 2, 1.0),
            ("description", "human", 5, 2.0),
            ("description", "like", 3, 1.0),
            ("description", "like", 5, 3.0),
            ("description", "eyes", 4, 1.0),
            ("description", "eyes", 5, 4.0),
            ("title", "manul", 1, 1.0),
            ("title", "manul", 5, 5.0),
            ("title", "cat", 2, 1.0),
            ("title", "cat", 5, 6.0),
            ("title", "facial", 3, 1.0),
            ("title", "facial", 5, 7.0),
            ("title", "expression", 4, 1.0),
            ("title", "expression", 5, 8.0),
        ]
        .iter()
        .cloned()
        .collect();

        let terms = [
            Term {
                field: "description",
                token: "very",
                weight: 1.0,
            },
            Term {
                field: "description",
                token: "human",
                weight: 10.0,
            },
            Term {
                field: "description",
                token: "like",
                weight: 100.0,
            },
            Term {
                field: "description",
                token: "eyes",
                weight: 1_000.0,
            },
            Term {
                field: "title",
                token: "manul",
                weight: 10_000.0,
            },
            Term {
                field: "title",
                token: "cat",
                weight: 100_000.0,
            },
            Term {
                field: "title",
                token: "facial",
                weight: 1_000_000.0,
            },
            Term {
                field: "title",
                token: "expression",
                weight: 10_000_000.0,
            },
        ];

        let and = And::new(&inverted_index, &terms);
        let actual: Vec<ScoredDoc> = and.collect();
        assert_eq!(
            actual,
            vec![ScoredDoc {
                id: 5,
                score: 87_654_321.0
            }]
        )
    }
}
<|endoftext|>


<commit_before>use std::borrow::ToOwned;
use std::collections::HashSet;
use std::fs;
use std::io::{Read,Write};
use std::io;
use std::path;
use std::process;
use std::convert::AsRef;

use atomicwrites::{AtomicFile,DisallowOverwrite};
use email::rfc5322::Rfc5322Parser;
use uuid::Uuid;
use vobject::{Component,Property,parse_component,write_component};

use cli::Configuration;

pub trait CustomPathExt {
    fn metadata(&self) -> io::Result<fs::Metadata>;
    fn exists(&self) -> bool;
    fn is_file(&self) -> bool;
    fn is_dir(&self) -> bool;
    fn str_extension(&self) -> Option<&str>;
}

impl CustomPathExt for path::Path {
    fn metadata(&self) -> io::Result<fs::Metadata> { fs::metadata(self) }

    fn exists(&self) -> bool { fs::metadata(self).is_ok() }

    fn is_file(&self) -> bool {
        fs::metadata(self).map(|s| s.is_file()).unwrap_or(false)
    }
    fn is_dir(&self) -> bool {
        fs::metadata(self).map(|s| s.is_dir()).unwrap_or(false)
    }

    fn str_extension(&self) -> Option<&str> {
        self.extension().and_then(|x| x.to_str())
    }
}

pub fn handle_process(process: &mut process::Child) -> io::Result<()> {
    let exitcode = try!(process.wait());
    if !exitcode.success() {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            format!("{}", exitcode)
        ));
    };
    Ok(())
}

pub struct IndexIterator {
    linebuffer: Vec<String>
}

impl IndexIterator {
    fn new(output: &String) -> IndexIterator {
        let rv = output.split('\n').map(|x| x.to_string()).collect();
        IndexIterator {
            linebuffer: rv
        }
    }
}

impl Iterator for IndexIterator {
    type Item = IndexItem;

    fn next(&mut self) -> Option<IndexItem> {
        match self.linebuffer.pop() {
            Some(x) => Some(IndexItem::new(x)),
            None => None
        }
    }
}

pub struct IndexItem {
    pub email: String,
    pub name: String,
    pub filepath: Option<path::PathBuf>
}

impl IndexItem {
    fn new(line: String) -> IndexItem {
        let mut parts = line.split('\t');

        IndexItem {
            email: parts.next().unwrap_or("").to_string(),
            name: parts.next().unwrap_or("").to_string(),
            filepath: match parts.next() {
                Some(x) => Some(path::PathBuf::from(x)),
                None => None
            }
        }
    }
}

pub struct Contact {
    pub component: Component,
    pub path: path::PathBuf
}

impl Contact {
    pub fn from_file<P: AsRef<path::Path>>(path: P) -> io::Result<Contact> {
        let mut contact_file = try!(fs::File::open(&path));
        let contact_string = {
            let mut x = String::new();
            try!(contact_file.read_to_string(&mut x));
            x
        };

        let item = match parse_component(&contact_string[..]) {
            Ok(x) => x,
            Err(e) => return Err(io::Error::new(
                io::ErrorKind::Other,
                format!("Error while parsing contact: {}", e)
            ))
        };

        Ok(Contact { component: item, path: path.as_ref().to_owned() })
    }

    pub fn generate(fullname: Option<&str>, email: Option<&str>, dir: &path::Path) -> Contact {
        let (uid, contact_path) = {
            let mut uid;
            let mut contact_path;
            loop {
                uid = Uuid::new_v4().to_simple_string();
                contact_path = dir.join(&format!("{}.vcf", uid));
                if !(*contact_path).exists() {
                    break
                }
            };
            (uid, contact_path)
        };
        Contact { path: contact_path, component: generate_component(uid, fullname, email) }
    }

    pub fn write_create(&self) -> io::Result<()> {
        let string = write_component(&self.component);
        let af = AtomicFile::new(&self.path, DisallowOverwrite);

        af.write(|f| {
            f.write_all(string.as_bytes())
        })
    }
}

fn generate_component(uid: String, fullname: Option<&str>, email: Option<&str>) -> Component {
    let mut comp = Component::new("VCARD");

    match fullname {
        Some(x) => comp.all_props_mut("FN").push(Property::new("FN", x)),
        None => ()
    };

    match email {
        Some(x) => comp.all_props_mut("EMAIL").push(Property::new("EMAIL", x)),
        None => ()
    };
    comp.all_props_mut("UID").push(Property::new("UID", &uid[..]));
    comp
}

pub fn index_query<'a>(config: &Configuration, query: &str) -> io::Result<IndexIterator> {
    let mut process = try!(
        command_from_config(&config.grep_cmd[..])
        .arg(&query[..])
        .stdin(process::Stdio::piped())
        .stdout(process::Stdio::piped())
        .stderr(process::Stdio::inherit())
        .spawn());

    {
        let mut index_fp = try!(fs::File::open(&config.index_path));
        let mut stdin = process.stdin.take().unwrap();
        let mut line: Vec<u8> = vec![];
        try!(index_fp.read_to_end(&mut line));
        try!(stdin.write_all(&line[..]));
    }

    try!(handle_process(&mut process));

    let stream = match process.stdout.as_mut() {
        Some(x) => x,
        None => return Err(io::Error::new(
            io::ErrorKind::Other,
            "Failed to get stdout from grep process.",
        ))
    };

    let mut output = String::new();
    try!(stream.read_to_string(&mut output));
    Ok(IndexIterator::new(&output))
}

/// Better than index_query if you're only interested in the filepath, as duplicate entries will be
/// removed.
pub fn file_query(config: &Configuration, query: &str) -> io::Result<HashSet<path::PathBuf>> {
    let mut rv: HashSet<path::PathBuf> = HashSet::new();
    rv.extend(
        try!(index_query(config, query)).filter_map(|x| x.filepath)
    );
    Ok(rv)
}

pub fn index_item_from_contact(contact: &Contact) -> io::Result<String> {
    let name = match contact.component.single_prop("FN") {
        Some(name) => name.value_as_string(),
        None => return Err(io::Error::new(
            io::ErrorKind::Other,
            "No name found.",
        ))
    };

    let emails = contact.component.all_props("EMAIL");
    let mut rv = String::new();
    for email in emails.iter() {
        rv.push_str(&format!("{}\t{}\t{}\n", email.value_as_string(), name, contact.path.display())[..]);
    };
    Ok(rv)
}

/// Return a tuple (fullname, email)
pub fn parse_from_header<'a>(s: &'a String) -> (Option<&'a str>, Option<&'a str>) {
    let mut split = s.rsplitn(1, ' ');
    let email = match split.next() {
        Some(x) => Some(x.trim_left_matches('<').trim_right_matches('>')),
        None => Some(&s[..])
    };
    let name = split.next();
    (name, email)
}

/// Given an email, return value of From header.
pub fn read_sender_from_email(email: &str) -> Option<String> {
    let mut parser = Rfc5322Parser::new(email);
    while !parser.eof() {
        match parser.consume_header() {
            Some(header) => {
                if header.name == "From" {
                    return header.get_value().ok()
                };
            },
            None => return None
        };
    };
    None
}

/// Write sender from given email as .vcf file to given directory.
pub fn add_contact_from_email(contact_dir: &path::Path, email_input: &str) -> io::Result<Contact> {
    let from_header = match read_sender_from_email(email_input) {
        Some(x) => x,
        None => return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "Couldn't find From-header in email.",
        ))
    };
    let (fullname, email) = parse_from_header(&from_header);
    let contact = Contact::generate(fullname, email, contact_dir);
    try!(contact.write_create());
    Ok(contact)
}

fn command_from_config(config_val: &str) -> process::Command {
    let mut parts = config_val.split(' ');
    let main = parts.next().unwrap();
    let rest: Vec<_> = parts.map(|x| x.to_string()).collect();
    let mut rv = process::Command::new(main);
    rv.args(&rest[..]);
    rv
}
<commit_msg>Add version to generated vcards<commit_after>use std::borrow::ToOwned;
use std::collections::HashSet;
use std::fs;
use std::io::{Read,Write};
use std::io;
use std::path;
use std::process;
use std::convert::AsRef;

use atomicwrites::{AtomicFile,DisallowOverwrite};
use email::rfc5322::Rfc5322Parser;
use uuid::Uuid;
use vobject::{Component,Property,parse_component,write_component};

use cli::Configuration;

pub trait CustomPathExt {
    fn metadata(&self) -> io::Result<fs::Metadata>;
    fn exists(&self) -> bool;
    fn is_file(&self) -> bool;
    fn is_dir(&self) -> bool;
    fn str_extension(&self) -> Option<&str>;
}

impl CustomPathExt for path::Path {
    fn metadata(&self) -> io::Result<fs::Metadata> { fs::metadata(self) }

    fn exists(&self) -> bool { fs::metadata(self).is_ok() }

    fn is_file(&self) -> bool {
        fs::metadata(self).map(|s| s.is_file()).unwrap_or(false)
    }
    fn is_dir(&self) -> bool {
        fs::metadata(self).map(|s| s.is_dir()).unwrap_or(false)
    }

    fn str_extension(&self) -> Option<&str> {
        self.extension().and_then(|x| x.to_str())
    }
}

pub fn handle_process(process: &mut process::Child) -> io::Result<()> {
    let exitcode = try!(process.wait());
    if !exitcode.success() {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            format!("{}", exitcode)
        ));
    };
    Ok(())
}

pub struct IndexIterator {
    linebuffer: Vec<String>
}

impl IndexIterator {
    fn new(output: &String) -> IndexIterator {
        let rv = output.split('\n').map(|x| x.to_string()).collect();
        IndexIterator {
            linebuffer: rv
        }
    }
}

impl Iterator for IndexIterator {
    type Item = IndexItem;

    fn next(&mut self) -> Option<IndexItem> {
        match self.linebuffer.pop() {
            Some(x) => Some(IndexItem::new(x)),
            None => None
        }
    }
}

pub struct IndexItem {
    pub email: String,
    pub name: String,
    pub filepath: Option<path::PathBuf>
}

impl IndexItem {
    fn new(line: String) -> IndexItem {
        let mut parts = line.split('\t');

        IndexItem {
            email: parts.next().unwrap_or("").to_string(),
            name: parts.next().unwrap_or("").to_string(),
            filepath: match parts.next() {
                Some(x) => Some(path::PathBuf::from(x)),
                None => None
            }
        }
    }
}

pub struct Contact {
    pub component: Component,
    pub path: path::PathBuf
}

impl Contact {
    pub fn from_file<P: AsRef<path::Path>>(path: P) -> io::Result<Contact> {
        let mut contact_file = try!(fs::File::open(&path));
        let contact_string = {
            let mut x = String::new();
            try!(contact_file.read_to_string(&mut x));
            x
        };

        let item = match parse_component(&contact_string[..]) {
            Ok(x) => x,
            Err(e) => return Err(io::Error::new(
                io::ErrorKind::Other,
                format!("Error while parsing contact: {}", e)
            ))
        };

        Ok(Contact { component: item, path: path.as_ref().to_owned() })
    }

    pub fn generate(fullname: Option<&str>, email: Option<&str>, dir: &path::Path) -> Contact {
        let (uid, contact_path) = {
            let mut uid;
            let mut contact_path;
            loop {
                uid = Uuid::new_v4().to_simple_string();
                contact_path = dir.join(&format!("{}.vcf", uid));
                if !(*contact_path).exists() {
                    break
                }
            };
            (uid, contact_path)
        };
        Contact { path: contact_path, component: generate_component(uid, fullname, email) }
    }

    pub fn write_create(&self) -> io::Result<()> {
        let string = write_component(&self.component);
        let af = AtomicFile::new(&self.path, DisallowOverwrite);

        af.write(|f| {
            f.write_all(string.as_bytes())
        })
    }
}

fn generate_component(uid: String, fullname: Option<&str>, email: Option<&str>) -> Component {
    let mut comp = Component::new("VCARD");

    comp.all_props_mut("VERSION").push(Property::new("VERSION", "3.0"));

    match fullname {
        Some(x) => comp.all_props_mut("FN").push(Property::new("FN", x)),
        None => ()
    };

    match email {
        Some(x) => comp.all_props_mut("EMAIL").push(Property::new("EMAIL", x)),
        None => ()
    };
    comp.all_props_mut("UID").push(Property::new("UID", &uid[..]));
    comp
}

pub fn index_query<'a>(config: &Configuration, query: &str) -> io::Result<IndexIterator> {
    let mut process = try!(
        command_from_config(&config.grep_cmd[..])
        .arg(&query[..])
        .stdin(process::Stdio::piped())
        .stdout(process::Stdio::piped())
        .stderr(process::Stdio::inherit())
        .spawn());

    {
        let mut index_fp = try!(fs::File::open(&config.index_path));
        let mut stdin = process.stdin.take().unwrap();
        let mut line: Vec<u8> = vec![];
        try!(index_fp.read_to_end(&mut line));
        try!(stdin.write_all(&line[..]));
    }

    try!(handle_process(&mut process));

    let stream = match process.stdout.as_mut() {
        Some(x) => x,
        None => return Err(io::Error::new(
            io::ErrorKind::Other,
            "Failed to get stdout from grep process.",
        ))
    };

    let mut output = String::new();
    try!(stream.read_to_string(&mut output));
    Ok(IndexIterator::new(&output))
}

/// Better than index_query if you're only interested in the filepath, as duplicate entries will be
/// removed.
pub fn file_query(config: &Configuration, query: &str) -> io::Result<HashSet<path::PathBuf>> {
    let mut rv: HashSet<path::PathBuf> = HashSet::new();
    rv.extend(
        try!(index_query(config, query)).filter_map(|x| x.filepath)
    );
    Ok(rv)
}

pub fn index_item_from_contact(contact: &Contact) -> io::Result<String> {
    let name = match contact.component.single_prop("FN") {
        Some(name) => name.value_as_string(),
        None => return Err(io::Error::new(
            io::ErrorKind::Other,
            "No name found.",
        ))
    };

    let emails = contact.component.all_props("EMAIL");
    let mut rv = String::new();
    for email in emails.iter() {
        rv.push_str(&format!("{}\t{}\t{}\n", email.value_as_string(), name, contact.path.display())[..]);
    };
    Ok(rv)
}

/// Return a tuple (fullname, email)
pub fn parse_from_header<'a>(s: &'a String) -> (Option<&'a str>, Option<&'a str>) {
    let mut split = s.rsplitn(1, ' ');
    let email = match split.next() {
        Some(x) => Some(x.trim_left_matches('<').trim_right_matches('>')),
        None => Some(&s[..])
    };
    let name = split.next();
    (name, email)
}

/// Given an email, return value of From header.
pub fn read_sender_from_email(email: &str) -> Option<String> {
    let mut parser = Rfc5322Parser::new(email);
    while !parser.eof() {
        match parser.consume_header() {
            Some(header) => {
                if header.name == "From" {
                    return header.get_value().ok()
                };
            },
            None => return None
        };
    };
    None
}

/// Write sender from given email as .vcf file to given directory.
pub fn add_contact_from_email(contact_dir: &path::Path, email_input: &str) -> io::Result<Contact> {
    let from_header = match read_sender_from_email(email_input) {
        Some(x) => x,
        None => return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "Couldn't find From-header in email.",
        ))
    };
    let (fullname, email) = parse_from_header(&from_header);
    let contact = Contact::generate(fullname, email, contact_dir);
    try!(contact.write_create());
    Ok(contact)
}

fn command_from_config(config_val: &str) -> process::Command {
    let mut parts = config_val.split(' ');
    let main = parts.next().unwrap();
    let rest: Vec<_> = parts.map(|x| x.to_string()).collect();
    let mut rv = process::Command::new(main);
    rv.args(&rest[..]);
    rv
}
<|endoftext|>


<commit_before>use cssparser::{
    self,
    parse_important,
    AtRuleParser,
    CowRcStr,
    DeclarationParser,
    Parser,
    ParserInput,
};
use std::collections::hash_map::{Entry, Iter as HashMapIter};
use std::collections::HashMap;
use std::ptr;
use std::str;

use libc;
use markup5ever::{namespace_url, ns, LocalName, QualName};
use url::Url;

use glib::translate::*;
use glib_sys::{gboolean, gpointer, GList};

use crate::allowed_url::AllowedUrl;
use crate::croco::*;
use crate::error::*;
use crate::io::{self, BinaryData};
use crate::node::NodeData;
use crate::properties::{parse_attribute_value_into_parsed_property, ParsedProperty};
use crate::util::utf8_cstr;

/// A parsed CSS declaration (`name: value [!important]`)
pub struct Declaration {
    pub attribute: QualName,
    pub property: ParsedProperty,
    pub important: bool,
}

#[derive(Default)]
pub struct DeclarationList {
    // Maps property_name -> Declaration
    declarations: HashMap<QualName, Declaration>,
}

pub struct DeclParser;

impl<'i> DeclarationParser<'i> for DeclParser {
    type Declaration = Declaration;
    type Error = ValueErrorKind;

    fn parse_value<'t>(
        &mut self,
        name: CowRcStr<'i>,
        input: &mut Parser<'i, 't>,
    ) -> Result<Declaration, cssparser::ParseError<'i, ValueErrorKind>> {
        let attribute = QualName::new(None, ns!(svg), LocalName::from(name.as_ref()));
        let property = parse_attribute_value_into_parsed_property(&attribute, input, true)
            .map_err(|e| input.new_custom_error(e))?;

        let important = input.try_parse(parse_important).is_ok();

        Ok(Declaration {
            attribute,
            property,
            important,
        })
    }
}

impl<'i> AtRuleParser<'i> for DeclParser {
    type PreludeNoBlock = ();
    type PreludeBlock = ();
    type AtRule = Declaration;
    type Error = ValueErrorKind;
}

#[derive(Clone, Hash, PartialEq, Eq)]
pub struct Selector {
    name: String,
    specificity: u64,
}

impl Selector {
    fn new(s: &str, specificity: u64) -> Selector {
        Selector {
            name: s.to_string(),
            specificity,
        }
    }
}

/// Contains all the mappings of selectors to style declarations
/// that result from loading an SVG document.
#[derive(Default)]
pub struct CssRules {
    selectors_to_declarations: HashMap<Selector, DeclarationList>,
}

impl DeclarationList {
    fn add_declaration(&mut self, declaration: Declaration) {
        match self.declarations.entry(declaration.attribute.clone()) {
            Entry::Occupied(mut e) => {
                let decl = e.get_mut();

                if !decl.important {
                    *decl = declaration;
                }
            }

            Entry::Vacant(v) => {
                v.insert(declaration);
            }
        }
    }

    pub fn iter(&self) -> DeclarationListIter {
        DeclarationListIter(self.declarations.iter())
    }
}

pub struct DeclarationListIter<'a>(HashMapIter<'a, QualName, Declaration>);

impl<'a> Iterator for DeclarationListIter<'a> {
    type Item = &'a Declaration;

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|(_attribute, declaration)| declaration)
    }
}

impl CssRules {
    pub fn parse(&mut self, base_url: Option<&Url>, buf: &str) {
        if buf.is_empty() {
            return; // libcroco doesn't like empty strings :(
        }

        unsafe {
            let mut handler_data = DocHandlerData {
                base_url,
                css_rules: self,
                selector: ptr::null_mut(),
            };

            let doc_handler = cr_doc_handler_new();
            init_cr_doc_handler(&mut *doc_handler);

            (*doc_handler).app_data = &mut handler_data as *mut _ as gpointer;

            let buf_ptr = buf.as_ptr() as *mut _;
            let buf_len = buf.len() as libc::c_ulong;

            let parser = cr_parser_new_from_buf(buf_ptr, buf_len, CR_UTF_8, false.to_glib());
            if parser.is_null() {
                cr_doc_handler_unref(doc_handler);
                return;
            }

            cr_parser_set_sac_handler(parser, doc_handler);
            cr_doc_handler_unref(doc_handler);

            cr_parser_set_use_core_grammar(parser, false.to_glib());
            cr_parser_parse(parser);

            cr_parser_destroy(parser);
        }
    }

    pub fn load_css(&mut self, aurl: &AllowedUrl) -> Result<(), LoadingError> {
        io::acquire_data(aurl, None)
            .and_then(|data| {
                let BinaryData {
                    data: bytes,
                    content_type,
                } = data;

                if content_type.as_ref().map(String::as_ref) == Some("text/css") {
                    Ok(bytes)
                } else {
                    rsvg_log!("\"{}\" is not of type text/css; ignoring", aurl);
                    Err(LoadingError::BadCss)
                }
            })
            .and_then(|bytes| {
                String::from_utf8(bytes).map_err(|_| {
                    rsvg_log!(
                        "\"{}\" does not contain valid UTF-8 CSS data; ignoring",
                        aurl
                    );
                    LoadingError::BadCss
                })
            })
            .and_then(|utf8| {
                self.parse(Some(&aurl), &utf8);
                Ok(()) // FIXME: return CSS parsing errors
            })
    }

    fn add_declaration(&mut self, selector: Selector, declaration: Declaration) {
        let decl_list = self
            .selectors_to_declarations
            .entry(selector)
            .or_insert_with(DeclarationList::default);

        decl_list.add_declaration(declaration);
    }

    pub fn get_declarations(&self, selector: &Selector) -> Option<&DeclarationList> {
        self.selectors_to_declarations.get(selector)
    }

    fn selector_matches_node(&self, selector: &Selector, node_data: &NodeData) -> bool {
        // Try to properly support all of the following, including inheritance:
        // *
        // #id
        // tag
        // tag#id
        // tag.class
        // tag.class#id
        //
        // This is basically a semi-compliant CSS2 selection engine

        let element_name = node_data.element_name().local.as_ref();
        let id = node_data.get_id();

        // *
        if selector.name == "*" {
            return true;
        }

        // tag
        if selector.name == element_name {
            return true;
        }

        if let Some(class) = node_data.get_class() {
            for cls in class.split_whitespace() {
                if !cls.is_empty() {
                    // tag.class#id
                    if let Some(id) = id {
                        let target = format!("{}.{}#{}", element_name, cls, id);
                        if selector.name == target {
                            return true;
                        }
                    }

                    // .class#id
                    if let Some(id) = id {
                        let target = format!(".{}#{}", cls, id);
                        if selector.name == target {
                            return true;
                        }
                    }

                    // tag.class
                    let target = format!("{}.{}", element_name, cls);
                    if selector.name == target {
                        return true;
                    }

                    // didn't find anything more specific, just apply the class style
                    let target = format!(".{}", cls);
                    if selector.name == target {
                        return true;
                    }
                }
            }
        }

        if let Some(id) = id {
            // id
            let target = format!("#{}", id);
            if selector.name == target {
                return true;
            }

            // tag#id
            let target = format!("{}#{}", element_name, id);
            if selector.name == target {
                return true;
            }
        }

        false
    }

    pub fn get_matches(&self, node_data: &NodeData) -> Vec<Selector> {
        let mut matches: Vec<_> = self.selectors_to_declarations
            .iter()
            .filter_map(|(selector, _)| {
                if self.selector_matches_node(selector, node_data) {
                    Some(selector)
                } else {
                    None
                }
            })
            .map(Selector::clone)
            .collect();

        matches.as_mut_slice().sort_by(|sel_a, sel_b| sel_a.specificity.cmp(&sel_b.specificity));

        matches
    }
}

struct DocHandlerData<'a> {
    base_url: Option<&'a Url>,
    css_rules: &'a mut CssRules,
    selector: *mut CRSelector,
}

macro_rules! get_doc_handler_data {
    ($doc_handler:expr) => {
        &mut *((*$doc_handler).app_data as *mut DocHandlerData)
    };
}

fn init_cr_doc_handler(handler: &mut CRDocHandler) {
    handler.import_style = Some(css_import_style);
    handler.start_selector = Some(css_start_selector);
    handler.end_selector = Some(css_end_selector);
    handler.property = Some(css_property);
    handler.error = Some(css_error);
    handler.unrecoverable_error = Some(css_unrecoverable_error);
}

unsafe extern "C" fn css_import_style(
    a_this: *mut CRDocHandler,
    _a_media_list: *mut GList,
    a_uri: CRString,
    _a_uri_default_ns: CRString,
    _a_location: CRParsingLocation,
) {
    let handler_data = get_doc_handler_data!(a_this);

    if a_uri.is_null() {
        return;
    }

    let raw_uri = cr_string_peek_raw_str(a_uri);
    let uri = utf8_cstr(raw_uri);

    if let Ok(aurl) = AllowedUrl::from_href(uri, handler_data.base_url) {
        // FIXME: handle CSS errors
        let _ = handler_data.css_rules.load_css(&aurl);
    } else {
        rsvg_log!("disallowed URL \"{}\" for importing CSS", uri);
    }
}

unsafe extern "C" fn css_start_selector(
    a_this: *mut CRDocHandler,
    a_selector_list: *mut CRSelector,
) {
    let handler_data = get_doc_handler_data!(a_this);

    cr_selector_ref(a_selector_list);
    handler_data.selector = a_selector_list;
}

unsafe extern "C" fn css_end_selector(
    a_this: *mut CRDocHandler,
    _a_selector_list: *mut CRSelector,
) {
    let handler_data = get_doc_handler_data!(a_this);

    cr_selector_unref(handler_data.selector);
    handler_data.selector = ptr::null_mut();
}

unsafe extern "C" fn css_property(
    a_this: *mut CRDocHandler,
    a_name: CRString,
    a_expression: CRTerm,
    a_is_important: gboolean,
) {
    let handler_data = get_doc_handler_data!(a_this);

    if a_name.is_null() || a_expression.is_null() || handler_data.selector.is_null() {
        return;
    }

    let mut cur_sel = handler_data.selector;
    while !cur_sel.is_null() {
        let simple_sel = (*cur_sel).simple_sel;

        cur_sel = (*cur_sel).next;

        if !simple_sel.is_null() {
            if cr_simple_sel_compute_specificity(simple_sel) != CR_OK {
                continue;
            }

            let specificity = u64::from((*simple_sel).specificity);

            let raw_selector_name = cr_simple_sel_to_string(simple_sel) as *mut libc::c_char;

            if !raw_selector_name.is_null() {
                let raw_prop_name = cr_string_peek_raw_str(a_name);
                let prop_name = utf8_cstr(raw_prop_name);

                let prop_value =
                    <String as FromGlibPtrFull<_>>::from_glib_full(cr_term_to_string(a_expression));

                let selector_name =
                    <String as FromGlibPtrFull<_>>::from_glib_full(raw_selector_name);

                let important = from_glib(a_is_important);

                let attribute = QualName::new(None, ns!(svg), LocalName::from(prop_name));

                let mut input = ParserInput::new(&prop_value);
                let mut parser = Parser::new(&mut input);

                match parse_attribute_value_into_parsed_property(&attribute, &mut parser, true) {
                    Ok(property) => {
                        let declaration = Declaration {
                            attribute,
                            property,
                            important,
                        };

                        handler_data
                            .css_rules
                            .add_declaration(Selector::new(&selector_name, specificity), declaration);
                    }
                    Err(_) => (), // invalid property name or invalid value; ignore
                }
            }
        }
    }
}

unsafe extern "C" fn css_error(_a_this: *mut CRDocHandler) {
    println!("CSS parsing error");
}

unsafe extern "C" fn css_unrecoverable_error(_a_this: *mut CRDocHandler) {
    println!("CSS unrecoverable error");
}
<commit_msg>Start defining the types that SelectorImpl requires<commit_after>use cssparser::{
    self,
    parse_important,
    AtRuleParser,
    CowRcStr,
    DeclarationParser,
    Parser,
    ParserInput,
    ToCss,
};
use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
use selectors::matching::{ElementSelectorFlags, MatchingContext};
use selectors::{self, OpaqueElement, SelectorImpl};

use std::collections::hash_map::{Entry, Iter as HashMapIter};
use std::collections::HashMap;
use std::fmt;
use std::ptr;
use std::str;

use libc;
use markup5ever::{namespace_url, ns, LocalName, Namespace, Prefix, QualName};
use url::Url;

use glib::translate::*;
use glib_sys::{gboolean, gpointer, GList};

use crate::allowed_url::AllowedUrl;
use crate::croco::*;
use crate::error::*;
use crate::io::{self, BinaryData};
use crate::node::NodeData;
use crate::properties::{parse_attribute_value_into_parsed_property, ParsedProperty};
use crate::text::NodeChars;
use crate::util::utf8_cstr;

/// A parsed CSS declaration (`name: value [!important]`)
pub struct Declaration {
    pub attribute: QualName,
    pub property: ParsedProperty,
    pub important: bool,
}

#[derive(Default)]
pub struct DeclarationList {
    // Maps property_name -> Declaration
    declarations: HashMap<QualName, Declaration>,
}

pub struct DeclParser;

impl<'i> DeclarationParser<'i> for DeclParser {
    type Declaration = Declaration;
    type Error = ValueErrorKind;

    fn parse_value<'t>(
        &mut self,
        name: CowRcStr<'i>,
        input: &mut Parser<'i, 't>,
    ) -> Result<Declaration, cssparser::ParseError<'i, ValueErrorKind>> {
        let attribute = QualName::new(None, ns!(svg), LocalName::from(name.as_ref()));
        let property = parse_attribute_value_into_parsed_property(&attribute, input, true)
            .map_err(|e| input.new_custom_error(e))?;

        let important = input.try_parse(parse_important).is_ok();

        Ok(Declaration {
            attribute,
            property,
            important,
        })
    }
}

impl<'i> AtRuleParser<'i> for DeclParser {
    type PreludeNoBlock = ();
    type PreludeBlock = ();
    type AtRule = Declaration;
    type Error = ValueErrorKind;
}

/// Dummy type required by the SelectorImpl trait
#[derive(Clone, Debug, Eq, PartialEq)]
struct NonTSPseudoClass;

impl ToCss for NonTSPseudoClass {
    fn to_css<W>(&self, _dest: &mut W) -> fmt::Result where W: fmt::Write {
        Ok(())
    }
}

impl selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
    type Impl = RsvgSelectors;

    fn is_active_or_hover(&self) -> bool {
        false
    }

    fn is_user_action_state(&self) -> bool {
        false
    }
}

/// Dummy type required by the SelectorImpl trait
#[derive(Clone, Debug, Eq, PartialEq)]
struct PseudoElement;

impl ToCss for PseudoElement {
    fn to_css<W>(&self, _dest: &mut W) -> fmt::Result where W: fmt::Write {
        Ok(())
    }
}

impl selectors::parser::PseudoElement for PseudoElement {
    type Impl = RsvgSelectors;
}

/// Holds all the types for the SelectorImpl trait
#[derive(Debug, Clone)]
struct RsvgSelectors;

impl SelectorImpl for RsvgSelectors {
    type ExtraMatchingData = ();
    type AttrValue = String;
    type Identifier = LocalName;
    type ClassName = LocalName;
    type PartName = LocalName;
    type LocalName = LocalName;
    type NamespaceUrl = Namespace;
    type NamespacePrefix = Prefix;
    type BorrowedNamespaceUrl = Namespace;
    type BorrowedLocalName = LocalName;
    type NonTSPseudoClass = NonTSPseudoClass;
    type PseudoElement = PseudoElement;
}

#[derive(Clone, Hash, PartialEq, Eq)]
pub struct Selector {
    name: String,
    specificity: u64,
}

impl Selector {
    fn new(s: &str, specificity: u64) -> Selector {
        Selector {
            name: s.to_string(),
            specificity,
        }
    }
}

/// Contains all the mappings of selectors to style declarations
/// that result from loading an SVG document.
#[derive(Default)]
pub struct CssRules {
    selectors_to_declarations: HashMap<Selector, DeclarationList>,
}

impl DeclarationList {
    fn add_declaration(&mut self, declaration: Declaration) {
        match self.declarations.entry(declaration.attribute.clone()) {
            Entry::Occupied(mut e) => {
                let decl = e.get_mut();

                if !decl.important {
                    *decl = declaration;
                }
            }

            Entry::Vacant(v) => {
                v.insert(declaration);
            }
        }
    }

    pub fn iter(&self) -> DeclarationListIter {
        DeclarationListIter(self.declarations.iter())
    }
}

pub struct DeclarationListIter<'a>(HashMapIter<'a, QualName, Declaration>);

impl<'a> Iterator for DeclarationListIter<'a> {
    type Item = &'a Declaration;

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|(_attribute, declaration)| declaration)
    }
}

impl CssRules {
    pub fn parse(&mut self, base_url: Option<&Url>, buf: &str) {
        if buf.is_empty() {
            return; // libcroco doesn't like empty strings :(
        }

        unsafe {
            let mut handler_data = DocHandlerData {
                base_url,
                css_rules: self,
                selector: ptr::null_mut(),
            };

            let doc_handler = cr_doc_handler_new();
            init_cr_doc_handler(&mut *doc_handler);

            (*doc_handler).app_data = &mut handler_data as *mut _ as gpointer;

            let buf_ptr = buf.as_ptr() as *mut _;
            let buf_len = buf.len() as libc::c_ulong;

            let parser = cr_parser_new_from_buf(buf_ptr, buf_len, CR_UTF_8, false.to_glib());
            if parser.is_null() {
                cr_doc_handler_unref(doc_handler);
                return;
            }

            cr_parser_set_sac_handler(parser, doc_handler);
            cr_doc_handler_unref(doc_handler);

            cr_parser_set_use_core_grammar(parser, false.to_glib());
            cr_parser_parse(parser);

            cr_parser_destroy(parser);
        }
    }

    pub fn load_css(&mut self, aurl: &AllowedUrl) -> Result<(), LoadingError> {
        io::acquire_data(aurl, None)
            .and_then(|data| {
                let BinaryData {
                    data: bytes,
                    content_type,
                } = data;

                if content_type.as_ref().map(String::as_ref) == Some("text/css") {
                    Ok(bytes)
                } else {
                    rsvg_log!("\"{}\" is not of type text/css; ignoring", aurl);
                    Err(LoadingError::BadCss)
                }
            })
            .and_then(|bytes| {
                String::from_utf8(bytes).map_err(|_| {
                    rsvg_log!(
                        "\"{}\" does not contain valid UTF-8 CSS data; ignoring",
                        aurl
                    );
                    LoadingError::BadCss
                })
            })
            .and_then(|utf8| {
                self.parse(Some(&aurl), &utf8);
                Ok(()) // FIXME: return CSS parsing errors
            })
    }

    fn add_declaration(&mut self, selector: Selector, declaration: Declaration) {
        let decl_list = self
            .selectors_to_declarations
            .entry(selector)
            .or_insert_with(DeclarationList::default);

        decl_list.add_declaration(declaration);
    }

    pub fn get_declarations(&self, selector: &Selector) -> Option<&DeclarationList> {
        self.selectors_to_declarations.get(selector)
    }

    fn selector_matches_node(&self, selector: &Selector, node_data: &NodeData) -> bool {
        // Try to properly support all of the following, including inheritance:
        // *
        // #id
        // tag
        // tag#id
        // tag.class
        // tag.class#id
        //
        // This is basically a semi-compliant CSS2 selection engine

        let element_name = node_data.element_name().local.as_ref();
        let id = node_data.get_id();

        // *
        if selector.name == "*" {
            return true;
        }

        // tag
        if selector.name == element_name {
            return true;
        }

        if let Some(class) = node_data.get_class() {
            for cls in class.split_whitespace() {
                if !cls.is_empty() {
                    // tag.class#id
                    if let Some(id) = id {
                        let target = format!("{}.{}#{}", element_name, cls, id);
                        if selector.name == target {
                            return true;
                        }
                    }

                    // .class#id
                    if let Some(id) = id {
                        let target = format!(".{}#{}", cls, id);
                        if selector.name == target {
                            return true;
                        }
                    }

                    // tag.class
                    let target = format!("{}.{}", element_name, cls);
                    if selector.name == target {
                        return true;
                    }

                    // didn't find anything more specific, just apply the class style
                    let target = format!(".{}", cls);
                    if selector.name == target {
                        return true;
                    }
                }
            }
        }

        if let Some(id) = id {
            // id
            let target = format!("#{}", id);
            if selector.name == target {
                return true;
            }

            // tag#id
            let target = format!("{}#{}", element_name, id);
            if selector.name == target {
                return true;
            }
        }

        false
    }

    pub fn get_matches(&self, node_data: &NodeData) -> Vec<Selector> {
        let mut matches: Vec<_> = self.selectors_to_declarations
            .iter()
            .filter_map(|(selector, _)| {
                if self.selector_matches_node(selector, node_data) {
                    Some(selector)
                } else {
                    None
                }
            })
            .map(Selector::clone)
            .collect();

        matches.as_mut_slice().sort_by(|sel_a, sel_b| sel_a.specificity.cmp(&sel_b.specificity));

        matches
    }
}

struct DocHandlerData<'a> {
    base_url: Option<&'a Url>,
    css_rules: &'a mut CssRules,
    selector: *mut CRSelector,
}

macro_rules! get_doc_handler_data {
    ($doc_handler:expr) => {
        &mut *((*$doc_handler).app_data as *mut DocHandlerData)
    };
}

fn init_cr_doc_handler(handler: &mut CRDocHandler) {
    handler.import_style = Some(css_import_style);
    handler.start_selector = Some(css_start_selector);
    handler.end_selector = Some(css_end_selector);
    handler.property = Some(css_property);
    handler.error = Some(css_error);
    handler.unrecoverable_error = Some(css_unrecoverable_error);
}

unsafe extern "C" fn css_import_style(
    a_this: *mut CRDocHandler,
    _a_media_list: *mut GList,
    a_uri: CRString,
    _a_uri_default_ns: CRString,
    _a_location: CRParsingLocation,
) {
    let handler_data = get_doc_handler_data!(a_this);

    if a_uri.is_null() {
        return;
    }

    let raw_uri = cr_string_peek_raw_str(a_uri);
    let uri = utf8_cstr(raw_uri);

    if let Ok(aurl) = AllowedUrl::from_href(uri, handler_data.base_url) {
        // FIXME: handle CSS errors
        let _ = handler_data.css_rules.load_css(&aurl);
    } else {
        rsvg_log!("disallowed URL \"{}\" for importing CSS", uri);
    }
}

unsafe extern "C" fn css_start_selector(
    a_this: *mut CRDocHandler,
    a_selector_list: *mut CRSelector,
) {
    let handler_data = get_doc_handler_data!(a_this);

    cr_selector_ref(a_selector_list);
    handler_data.selector = a_selector_list;
}

unsafe extern "C" fn css_end_selector(
    a_this: *mut CRDocHandler,
    _a_selector_list: *mut CRSelector,
) {
    let handler_data = get_doc_handler_data!(a_this);

    cr_selector_unref(handler_data.selector);
    handler_data.selector = ptr::null_mut();
}

unsafe extern "C" fn css_property(
    a_this: *mut CRDocHandler,
    a_name: CRString,
    a_expression: CRTerm,
    a_is_important: gboolean,
) {
    let handler_data = get_doc_handler_data!(a_this);

    if a_name.is_null() || a_expression.is_null() || handler_data.selector.is_null() {
        return;
    }

    let mut cur_sel = handler_data.selector;
    while !cur_sel.is_null() {
        let simple_sel = (*cur_sel).simple_sel;

        cur_sel = (*cur_sel).next;

        if !simple_sel.is_null() {
            if cr_simple_sel_compute_specificity(simple_sel) != CR_OK {
                continue;
            }

            let specificity = u64::from((*simple_sel).specificity);

            let raw_selector_name = cr_simple_sel_to_string(simple_sel) as *mut libc::c_char;

            if !raw_selector_name.is_null() {
                let raw_prop_name = cr_string_peek_raw_str(a_name);
                let prop_name = utf8_cstr(raw_prop_name);

                let prop_value =
                    <String as FromGlibPtrFull<_>>::from_glib_full(cr_term_to_string(a_expression));

                let selector_name =
                    <String as FromGlibPtrFull<_>>::from_glib_full(raw_selector_name);

                let important = from_glib(a_is_important);

                let attribute = QualName::new(None, ns!(svg), LocalName::from(prop_name));

                let mut input = ParserInput::new(&prop_value);
                let mut parser = Parser::new(&mut input);

                match parse_attribute_value_into_parsed_property(&attribute, &mut parser, true) {
                    Ok(property) => {
                        let declaration = Declaration {
                            attribute,
                            property,
                            important,
                        };

                        handler_data
                            .css_rules
                            .add_declaration(Selector::new(&selector_name, specificity), declaration);
                    }
                    Err(_) => (), // invalid property name or invalid value; ignore
                }
            }
        }
    }
}

unsafe extern "C" fn css_error(_a_this: *mut CRDocHandler) {
    println!("CSS parsing error");
}

unsafe extern "C" fn css_unrecoverable_error(_a_this: *mut CRDocHandler) {
    println!("CSS unrecoverable error");
}
<|endoftext|>