Commit 742515aa authored by Sebastian Dröge's avatar Sebastian Dröge 🍵
Browse files

Properly report error / error messages and make the trait APIs more Rust-like

parent f7fd9275
// Copyright (C) 2016 Sebastian Dröge <sebastian@centricular.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
// Boston, MA 02110-1301, USA.
//
//
use libc::c_char;
use std::os::raw::c_void;
use std::ffi::CString;
use std::ptr;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::fmt::Error as FmtError;
use std::borrow::Cow;
use utils::*;
macro_rules! error_msg(
// Plain strings
($err:expr, ($msg:expr), [$dbg:expr]) => {
ErrorMessage::new(&$err, Some(From::from($msg)),
Some(From::from($dbg)),
file!(), module_path!(), line!())
};
($err:expr, ($msg:expr)) => {
ErrorMessage::new(&$err, Some(From::from($msg)),
None,
file!(), module_path!(), line!())
};
($err:expr, [$dbg:expr]) => {
ErrorMessage::new(&$err, None,
Some(From::from($dbg)),
file!(), module_path!(), line!())
};
// Format strings
($err:expr, ($($msg:tt)*), [$($dbg:tt)*]) => { {
ErrorMessage::new(&$err, Some(From::from(format!($($msg)*))),
From::from(Some(format!($($dbg)*))),
file!(), module_path!(), line!())
}};
($err:expr, ($($msg:tt)*)) => { {
ErrorMessage::new(&$err, Some(From::from(format!($($msg)*))),
None,
file!(), module_path!(), line!())
}};
($err:expr, [$($dbg:tt)*]) => { {
ErrorMessage::new(&$err, None,
Some(From::from(format!($($dbg)*))),
file!(), module_path!(), line!())
}};
);
pub trait ToGError {
fn to_gerror(&self) -> (u32, i32);
}
pub fn gst_library_error_domain() -> u32 {
extern "C" {
fn gst_library_error_quark() -> u32;
}
unsafe { gst_library_error_quark() }
}
pub fn gst_resource_error_domain() -> u32 {
extern "C" {
fn gst_resource_error_quark() -> u32;
}
unsafe { gst_resource_error_quark() }
}
#[derive(Debug)]
pub struct ErrorMessage {
pub error_domain: u32,
pub error_code: i32,
pub message: Option<String>,
pub debug: Option<String>,
pub filename: &'static str,
pub function: &'static str,
pub line: u32,
}
impl ErrorMessage {
pub fn new<T: ToGError>(error: &T,
message: Option<Cow<str>>,
debug: Option<Cow<str>>,
filename: &'static str,
function: &'static str,
line: u32)
-> ErrorMessage {
let (gdomain, gcode) = error.to_gerror();
ErrorMessage {
error_domain: gdomain,
error_code: gcode,
message: message.map(|m| m.into_owned()),
debug: debug.map(|d| d.into_owned()),
filename: filename,
function: function,
line: line,
}
}
}
#[derive(Debug)]
pub enum FlowError {
NotLinked,
Flushing,
Eos,
NotNegotiated(ErrorMessage),
Error(ErrorMessage),
}
impl FlowError {
pub fn to_native(&self) -> GstFlowReturn {
match *self {
FlowError::NotLinked => GstFlowReturn::NotLinked,
FlowError::Flushing => GstFlowReturn::Flushing,
FlowError::Eos => GstFlowReturn::Eos,
FlowError::NotNegotiated(..) => GstFlowReturn::NotNegotiated,
FlowError::Error(..) => GstFlowReturn::Error,
}
}
}
impl Display for FlowError {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
match *self {
FlowError::NotLinked | FlowError::Flushing | FlowError::Eos => {
f.write_str(self.description())
}
FlowError::NotNegotiated(ref m) => {
f.write_fmt(format_args!("{}: {} ({})",
self.description(),
m.message.as_ref().map_or("None", |s| s.as_str()),
m.debug.as_ref().map_or("None", |s| s.as_str())))
}
FlowError::Error(ref m) => {
f.write_fmt(format_args!("{}: {} ({})",
self.description(),
m.message.as_ref().map_or("None", |s| s.as_str()),
m.debug.as_ref().map_or("None", |s| s.as_str())))
}
}
}
}
impl Error for FlowError {
fn description(&self) -> &str {
match *self {
FlowError::NotLinked => "Not Linked",
FlowError::Flushing => "Flushing",
FlowError::Eos => "Eos",
FlowError::NotNegotiated(..) => "Not Negotiated",
FlowError::Error(..) => "Error",
}
}
}
#[derive(Debug)]
pub enum UriErrorKind {
UnsupportedProtocol = 0,
BadUri,
BadState,
BadReference,
}
#[derive(Debug)]
pub struct UriError {
error_kind: UriErrorKind,
message: Option<String>,
}
impl UriError {
pub fn new(error_kind: UriErrorKind, message: Option<String>) -> UriError {
UriError {
error_kind: error_kind,
message: message,
}
}
pub fn message(&self) -> &Option<String> {
&self.message
}
pub fn kind(&self) -> &UriErrorKind {
&self.error_kind
}
pub unsafe fn into_gerror(self, err: *mut c_void) {
extern "C" {
fn g_set_error_literal(err: *mut c_void,
domain: u32,
code: i32,
message: *const c_char);
fn gst_uri_error_quark() -> u32;
}
if let Some(msg) = self.message {
let cmsg = CString::new(msg.as_str()).unwrap();
g_set_error_literal(err,
gst_uri_error_quark(),
self.error_kind as i32,
cmsg.as_ptr());
} else {
g_set_error_literal(err,
gst_uri_error_quark(),
self.error_kind as i32,
ptr::null());
}
}
}
impl Display for UriError {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
match self.message {
None => f.write_str(self.description()),
Some(ref message) => f.write_fmt(format_args!("{}: {}", self.description(), message)),
}
}
}
impl Error for UriError {
fn description(&self) -> &str {
match self.error_kind {
UriErrorKind::UnsupportedProtocol => "Unsupported protocol",
UriErrorKind::BadUri => "Bad URI",
UriErrorKind::BadState => "Bad State",
UriErrorKind::BadReference => "Bad Reference",
}
}
}
......@@ -24,6 +24,8 @@ extern crate hyper;
#[macro_use]
pub mod utils;
#[macro_use]
pub mod error;
pub mod rssource;
pub mod rssink;
pub mod rsfilesrc;
......
......@@ -22,10 +22,48 @@ use url::Url;
use std::io::Write;
use std::sync::Mutex;
use std::convert::From;
use utils::*;
use error::*;
use rssink::*;
macro_rules! error_msg(
// Format strings
($err:expr, ($($msg:tt)*), [$($dbg:tt)*]) => { {
ErrorMessage::new(&$err, Some(From::from(format!($($msg)*))),
From::from(Some(format!($($dbg)*))),
file!(), module_path!(), line!())
}};
($err:expr, ($($msg:tt)*)) => { {
ErrorMessage::new(&$err, Some(From::from(format!($($msg)*))),
None,
file!(), module_path!(), line!())
}};
($err:expr, [$($dbg:tt)*]) => { {
ErrorMessage::new(&$err, None,
Some(From::from(format!($($dbg)*))),
file!(), module_path!(), line!())
}};
// Plain strings
($err:expr, ($msg:expr), [$dbg:expr]) => {
ErrorMessage::new(&$err, Some(From::from($msg)),
Some(From::from($dbg)),
file!(), module_path!(), line!())
};
($err:expr, ($msg:expr)) => {
ErrorMessage::new(&$err, Some(From::from($msg)),
None,
file!(), module_path!(), line!())
};
($err:expr, [$dbg:expr]) => {
ErrorMessage::new(&$err, None,
Some(From::from($dbg)),
file!(), module_path!(), line!())
};
);
#[derive(Debug)]
struct Settings {
location: Option<PathBuf>,
......@@ -69,23 +107,16 @@ impl Sink for FileSink {
fn set_uri(&self, uri: Option<Url>) -> Result<(), UriError> {
let location = &mut self.settings.lock().unwrap().location;
*location = None;
match uri {
None => {
*location = None;
Ok(())
}
None => Ok(()),
Some(ref uri) => {
match uri.to_file_path().ok() {
Some(p) => {
*location = Some(p);
Ok(())
}
None => {
*location = None;
*location = Some(try!(uri.to_file_path()
.or_else(|_| {
Err(UriError::new(UriErrorKind::UnsupportedProtocol,
Some(format!("Unsupported file URI '{}'", uri.as_str()))))
}
}
})));
Ok(())
}
}
}
......@@ -97,59 +128,55 @@ impl Sink for FileSink {
.and_then(|i| i) // join()
}
fn start(&self) -> bool {
fn start(&self) -> Result<(), ErrorMessage> {
let location = &self.settings.lock().unwrap().location;
let mut streaming_state = self.streaming_state.lock().unwrap();
if let StreamingState::Started { .. } = *streaming_state {
return false;
return Err(error_msg!(SinkError::Failure, ["Sink already started"]));
}
match *location {
None => false,
Some(ref location) => {
match File::create(location.as_path()) {
Ok(file) => {
*streaming_state = StreamingState::Started {
file: file,
position: 0,
};
true
}
Err(err) => {
println_err!("Could not open file for writing '{}': {}",
location.to_str().unwrap_or("Non-UTF8 path"),
err.to_string());
false
}
}
}
}
let location = &try!(location.as_ref().ok_or_else(|| {
error_msg!(SinkError::Failure, ["No URI provided"])
}));
let file = try!(File::create(location.as_path()).or_else(|err| {
Err(error_msg!(SinkError::OpenFailed,
["Could not open file for writing '{}': {}",
location.to_str().unwrap_or("Non-UTF8 path"),
err.to_string()]))
}));
*streaming_state = StreamingState::Started {
file: file,
position: 0,
};
Ok(())
}
fn stop(&self) -> bool {
fn stop(&self) -> Result<(), ErrorMessage> {
let mut streaming_state = self.streaming_state.lock().unwrap();
*streaming_state = StreamingState::Stopped;
true
Ok(())
}
fn render(&self, data: &[u8]) -> GstFlowReturn {
fn render(&self, data: &[u8]) -> Result<(), FlowError> {
let mut streaming_state = self.streaming_state.lock().unwrap();
if let StreamingState::Started { ref mut file, ref mut position } = *streaming_state {
match file.write_all(data) {
Ok(_) => {
*position += data.len() as u64;
GstFlowReturn::Ok
}
Err(err) => {
println_err!("Failed to write: {}", err);
GstFlowReturn::Error
}
let (file, position) = match *streaming_state {
StreamingState::Started { ref mut file, ref mut position } => (file, position),
StreamingState::Stopped => {
return Err(FlowError::Error(error_msg!(SinkError::Failure, ["Not started yet"])));
}
} else {
GstFlowReturn::Error
}
};
try!(file.write_all(data).or_else(|err| {
Err(FlowError::Error(error_msg!(SinkError::WriteFailed, ["Failed to write: {}", err])))
}));
*position += data.len() as u64;
Ok(())
}
}
......@@ -22,9 +22,7 @@ use std::path::PathBuf;
use std::sync::Mutex;
use url::Url;
use std::io::Write;
use utils::*;
use error::*;
use rssource::*;
#[derive(Debug)]
......@@ -76,17 +74,12 @@ impl Source for FileSrc {
Ok(())
}
Some(ref uri) => {
match uri.to_file_path().ok() {
Some(p) => {
*location = Some(p);
Ok(())
}
None => {
*location = None;
*location = Some(try!(uri.to_file_path()
.or_else(|_| {
Err(UriError::new(UriErrorKind::UnsupportedProtocol,
Some(format!("Unsupported file URI '{}'", uri.as_str()))))
}
}
})));
Ok(())
}
}
}
......@@ -115,75 +108,71 @@ impl Source for FileSrc {
}
}
fn start(&self) -> bool {
fn start(&self) -> Result<(), ErrorMessage> {
let location = &self.settings.lock().unwrap().location;
let mut streaming_state = self.streaming_state.lock().unwrap();
if let StreamingState::Started { .. } = *streaming_state {
return false;
return Err(error_msg!(SourceError::Failure, ["Source already started"]));
}
match *location {
None => false,
Some(ref location) => {
match File::open(location.as_path()) {
Ok(file) => {
*streaming_state = StreamingState::Started {
file: file,
position: 0,
};
true
}
Err(err) => {
println_err!("Could not open file for writing '{}': {}",
location.to_str().unwrap_or("Non-UTF8 path"),
err.to_string());
false
}
}
}
}
let location = &try!(location.as_ref()
.ok_or_else(|| error_msg!(SourceError::Failure, ["No URI provided"])));
let file = try!(File::open(location.as_path()).or_else(|err| {
Err(error_msg!(SourceError::OpenFailed,
["Could not open file for reading '{}': {}",
location.to_str().unwrap_or("Non-UTF8 path"),
err.to_string()]))
}));
*streaming_state = StreamingState::Started {
file: file,
position: 0,
};
Ok(())
}
fn stop(&self) -> bool {
fn stop(&self) -> Result<(), ErrorMessage> {
let mut streaming_state = self.streaming_state.lock().unwrap();
*streaming_state = StreamingState::Stopped;
true
Ok(())
}
fn fill(&self, offset: u64, data: &mut [u8]) -> Result<usize, GstFlowReturn> {
fn fill(&self, offset: u64, data: &mut [u8]) -> Result<usize, FlowError> {
let mut streaming_state = self.streaming_state.lock().unwrap();
if let StreamingState::Started { ref mut file, ref mut position } = *streaming_state {
if *position != offset {
match file.seek(SeekFrom::Start(offset)) {
Ok(_) => {
*position = offset;
}
Err(err) => {
println_err!("Failed to seek to {}: {}", offset, err.to_string());
return Err(GstFlowReturn::Error);
}
}
}
match file.read(data) {
Ok(size) => {
*position += size as u64;
Ok(size)
}
Err(err) => {
println_err!("Failed to read at {}: {}", offset, err.to_string());
Err(GstFlowReturn::Error)
}
let (file, position) = match *streaming_state {
StreamingState::Started { ref mut file, ref mut position } => (file, position),
StreamingState::Stopped => {
return Err(FlowError::Error(error_msg!(SourceError::Failure, ["Not started yet"])));
}
} else {
Err(GstFlowReturn::Error)
};
if *position != offset {
try!(file.seek(SeekFrom::Start(offset)).or_else(|err| {
Err(FlowError::Error(error_msg!(SourceError::SeekFailed,
["Failed to seek to {}: {}",
offset,
err.to_string()])))
}));
*position = offset;
}
let size = try!(file.read(data).or_else(|err| {
Err(FlowError::Error(error_msg!(SourceError::ReadFailed,
["Failed to read at {}: {}", offset, err.to_string()])))
}));
*position += size as u64;
Ok(size)
}
fn do_seek(&self, _: u64, _: u64) -> bool {
true
fn do_seek(&self, _: u64, _: u64) -> Result<(), ErrorMessage> {
Ok(())
}
}
......@@ -23,10 +23,9 @@ use hyper::header::{ContentLength, ContentRange, ContentRangeSpec, Range, ByteRa
use hyper::client::Client;
use hyper::client::response::Response;
use std::io::Write;
use std::sync::Mutex;
use utils::*;
use error::*;
use rssource::*;
#[derive(Debug)]
......@@ -72,71 +71,70 @@ impl HttpSrc {
Box::new(HttpSrc::new(controller))
}
fn do_request(&self, start: u64, stop: u64) -> StreamingState {
fn do_request(&self, start: u64, stop: u64) -> Result<StreamingState, ErrorMessage> {
let url = &self.settings.lock().unwrap().url;
match *url {
None => StreamingState::Stopped,
Some(ref url) => {
let mut req = self.client.get(url.clone());
if start != 0 || stop != u64::MAX {
req = if stop == u64::MAX {
req.header(Range::Bytes(vec![ByteRangeSpec::AllFrom(start