<commit_before>#![feature(plugin)]
#![plugin(docopt_macros)]

extern crate ansi_term;
extern crate docopt;
extern crate duk;
extern crate env_logger;
#[macro_use]
extern crate log;
extern crate nix;
extern crate protobuf;
extern crate record_query;
extern crate rustc_serialize;
extern crate serde_protobuf;

use std::env;
use std::io;
use std::path;

use record_query as rq;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");

docopt!(pub Args derive Debug, concat!("
rq - record query v", env!("CARGO_PKG_VERSION"), "

A tool for manipulating data records.

Records are read from stdin, processed, and written to stdout.  The tool accepts
a query in the custom rq query language as its main command-line arguments.

See 'man rq' for in-depth documentation.

Usage:
  rq (--help|--version)
  rq [-j|-c|-p <type>] [-J|-C|-P <type>] [-l <spec>|-q] [-t] [--] [<query>]
  rq [-l <spec>|-q] [-t] protobuf add <schema> [--base <path>]

Options:
  --help
      Show this screen.
  --version
      Show the program name and version.

  -j, --input-json
      Input is white-space separated JSON values.
  -J, --output-json
      Output should be formatted as JSON values.
  -c, --input-cbor
      Input is a series of CBOR values.
  -C, --output-cbor
      Output is a series of CBOR values.
  -p <type>, --input-protobuf <type>
      Input is a single protocol buffer object.  The argument refers to the
      fully qualified name of the message type (including the leading '.').
  -P <type>, --output-protobuf <type>
      Output should be formatted as protocol buffer objects.  The argument
      refers to the fully qualified name of the message type (including the
      leading '.').

  <query>
      A query indicating how to transform each record.

  --base <path>
      Directories are significant when dealing with protocol buffer
      schemas.  This specifies the base directory used to normalize schema
      file paths [default: .]

  -l <spec>, --log <spec>
      Configure logging using the supplied specification, in the format of
      `env_logger`.  See: https://doc.rust-lang.org/log/env_logger

  -t, --trace
      Enable (back)trace output on error.

  -q, --quiet
      Log nothing (alias for '-l off').
"),
        flag_input_protobuf: Option<String>,
        flag_output_protobuf: Option<String>,
        flag_log: Option<String>,
        flag_base: Option<String>);

fn main() {
    let args: Args = Args::docopt()
                         .version(Some(VERSION.to_owned()))
                         .decode()
                         .unwrap_or_else(|e| e.exit());

    setup_log(args.flag_log.as_ref().map(String::as_ref), args.flag_quiet);

    main_with_args(&args).unwrap_or_else(|e| log_error(&args, e));
}

fn main_with_args(args: &Args) -> rq::error::Result<()> {
    let paths = rq::config::Paths::new().unwrap();

    if args.cmd_protobuf {
        if args.cmd_add {
            let schema = path::Path::new(&args.arg_schema);
            let base = path::Path::new(if let Some(ref b) = args.flag_base {
                b.as_str()
            } else {
                "."
            });
            rq::proto_index::add_file(&paths, base, schema)
        } else {
            unreachable!()
        }
    } else {
        run(&args, &paths)
    }
}

fn run(args: &Args, paths: &rq::config::Paths) -> rq::error::Result<()> {

    let stdin = io::stdin();
    let mut input = stdin.lock();

    if let Some(ref name) = args.flag_input_protobuf {
        let proto_descriptors = try!(load_descriptors(&paths));
        let stream = protobuf::CodedInputStream::new(&mut input);
        let source = try!(rq::value::protobuf::source(&proto_descriptors, name, stream));
        run_source(args, paths, source)
    } else if args.flag_input_cbor {
        let source = rq::value::cbor::source(&mut input);
        run_source(args, paths, source)
    } else {
        let source = rq::value::json::source(&mut input);
        run_source(args, paths, source)
    }
}

fn run_source<I>(args: &Args, paths: &rq::config::Paths, source: I) -> rq::error::Result<()>
    where I: rq::value::Source
{
    let mut output = io::stdout();

    if let Some(_) = args.flag_output_protobuf {
        Err(rq::error::Error::unimplemented("protobuf serialization".to_owned()))
    } else if args.flag_output_cbor {
        let sink = rq::value::cbor::sink(&mut output);
        run_source_sink(args, paths, source, sink)
    } else {
        let sink = rq::value::json::sink(&mut output);
        run_source_sink(args, paths, source, sink)
    }
}

fn run_source_sink<I, O>(args: &Args,
                         _paths: &rq::config::Paths,
                         source: I,
                         mut sink: O)
                         -> rq::error::Result<()>
    where I: rq::value::Source,
          O: rq::value::Sink
{
    use record_query::value::Source;

    let query = rq::query::Query::parse(if args.arg_query.is_empty() {
        "id"
    } else {
        &args.arg_query
    });
    let query_context = rq::query::Context::new();
    let mut results = try!(query.evaluate(&query_context, source));
    while let Some(result) = try!(results.read()) {
        try!(sink.write(result));
    }
    Ok(())
}

fn load_descriptors(paths: &rq::config::Paths)
                    -> rq::error::Result<serde_protobuf::descriptor::Descriptors> {
    let descriptors_proto = try!(rq::proto_index::compile_descriptor_set(paths));
    Ok(serde_protobuf::descriptor::Descriptors::from_proto(&descriptors_proto))
}

fn log_error(args: &Args, error: rq::error::Error) {
    use record_query::error::ErrorKind;

    match *error.kind() {
        ErrorKind::Msg(ref m) => error!("{}", m),
        ErrorKind::Duk(duk::ErrorKind::Js(ref e)) => {
            if let Some(ref stack) = e.stack {
                error!("Error while executing Javascript:");
                for line in stack.lines() {
                    error!("{}", line);
                }
            } else {
                error!("Error while executing Javascript: {}", e.message);
            }
        },
        _ => {
            error!("Encountered: {}", error);
            for e in error.iter().skip(1) {
                error!("Caused by: {}", e);
            }
        },
    }

    if args.flag_trace || env::var("RUST_BACKTRACE").as_ref().map(String::as_str) == Ok("1") {
        error!("");
        error!("Backtrace:");
        for line in format!("{:?}", error.backtrace()).lines() {
            error!("  {}", line);
        }
    } else {
        error!("(Re-run with (-t|--trace) or RUST_BACKTRACE=1 for a backtrace)");
    }
}

fn setup_log(spec: Option<&str>, quiet: bool) {
    use log::LogLevelFilter;

    let mut builder = env_logger::LogBuilder::new();

    if quiet {
        builder.filter(None, LogLevelFilter::Off);
    } else if let Some(s) = spec {
        builder.parse(s);
    } else if let Ok(s) = env::var("RUST_LOG") {
        builder.parse(&s);
    } else {
        builder.filter(None, LogLevelFilter::Info);
    };

    builder.format(format_log_record);

    builder.init().unwrap();
}

fn format_log_record(record: &log::LogRecord) -> String {
    use ansi_term::ANSIStrings;
    use ansi_term::Colour;
    use ansi_term::Style;
    use log::LogLevel;
    use nix::unistd;
    use nix::sys::ioctl;

    if unistd::isatty(ioctl::libc::STDERR_FILENO).unwrap_or(false) {
        let normal = Style::new();
        let (front, back) = match record.level() {
            LogLevel::Error => (Colour::Red.normal(), Colour::Red.dimmed()),
            LogLevel::Warn => (Colour::Yellow.normal(), Colour::Yellow.dimmed()),
            LogLevel::Info => (Colour::Blue.normal(), Colour::Blue.dimmed()),
            LogLevel::Debug => (Colour::Purple.normal(), Colour::Purple.dimmed()),
            LogLevel::Trace => (Colour::White.dimmed(), Colour::Black.normal()),
        };

        let strings = &[back.paint("["),
                        front.paint(format!("{}", record.level())),
                        back.paint("]"),
                        normal.paint(" "),
                        back.paint("["),
                        front.paint(record.location().module_path()),
                        back.paint("]"),
                        normal.paint(" "),
                        back.paint(format!("{}", record.args()))];

        format!("{}", ANSIStrings(strings))
    } else {
        format!("[{}] [{}] {}",
                record.level(),
                record.location().module_path(),
                record.args())
    }

}

#[cfg(test)]
mod test {
    use super::*;

    fn parse_args(args: &[&str]) -> Args {
        let a = Args::docopt().argv(args).decode().unwrap();
        println!("{:?}", a);
        a
    }

    #[test]
    fn test_docopt_kitchen_sink() {
        let a = parse_args(&["rq", "-l", "info", "-jP", ".foo.Bar", "select x"]);
        assert!(a.flag_input_json);
        assert_eq!(a.flag_output_protobuf, Some(".foo.Bar".to_owned()));
        assert_eq!(a.flag_log, Some("info".to_owned()));
        assert_eq!(a.arg_query, "select x");
    }

    #[test]
    fn test_docopt_no_args() {
        parse_args(&["rq"]);
    }

    #[test]
    #[should_panic(expected = "Help")]
    fn test_docopt_help() {
        parse_args(&["rq", "--help"]);
    }

    #[test]
    fn test_docopt_version() {
        let a = parse_args(&["rq", "--version"]);
        assert!(a.flag_version);
    }

    #[test]
    fn test_docopt_input_json() {
        let a = parse_args(&["rq", "-j"]);
        assert!(a.flag_input_json);
    }

    #[test]
    fn test_docopt_input_json_long() {
        let a = parse_args(&["rq", "--input-json"]);
        assert!(a.flag_input_json);
    }

    #[test]
    fn test_docopt_output_json() {
        let a = parse_args(&["rq", "-J"]);
        assert!(a.flag_output_json);
    }

    #[test]
    fn test_docopt_output_json_long() {
        let a = parse_args(&["rq", "--output-json"]);
        assert!(a.flag_output_json);
    }

    #[test]
    fn test_docopt_input_cbor() {
        let a = parse_args(&["rq", "-c"]);
        assert!(a.flag_input_cbor);
    }

    #[test]
    fn test_docopt_input_cbor_long() {
        let a = parse_args(&["rq", "--input-cbor"]);
        assert!(a.flag_input_cbor);
    }

    #[test]
    fn test_docopt_output_cbor() {
        let a = parse_args(&["rq", "-C"]);
        assert!(a.flag_output_cbor);
    }

    #[test]
    fn test_docopt_output_cbor_long() {
        let a = parse_args(&["rq", "--output-cbor"]);
        assert!(a.flag_output_cbor);
    }

    #[test]
    fn test_docopt_input_protobuf() {
        let a = parse_args(&["rq", "-p", ".foo.Bar"]);
        assert_eq!(a.flag_input_protobuf, Some(".foo.Bar".to_owned()));
    }

    #[test]
    fn test_docopt_input_protobuf_long() {
        let a = parse_args(&["rq", "--input-protobuf", ".foo.Bar"]);
        assert_eq!(a.flag_input_protobuf, Some(".foo.Bar".to_owned()));
    }

    #[test]
    fn test_docopt_output_protobuf() {
        let a = parse_args(&["rq", "-P", ".foo.Bar"]);
        assert_eq!(a.flag_output_protobuf, Some(".foo.Bar".to_owned()));
    }

    #[test]
    fn test_docopt_output_protobuf_long() {
        let a = parse_args(&["rq", "--output-protobuf", ".foo.Bar"]);
        assert_eq!(a.flag_output_protobuf, Some(".foo.Bar".to_owned()));
    }

    #[test]
    #[should_panic(expected = "NoMatch")]
    fn test_docopt_input_conflict() {
        parse_args(&["rq", "-jc"]);
    }

    #[test]
    #[should_panic(expected = "NoMatch")]
    fn test_docopt_output_conflict() {
        parse_args(&["rq", "-JC"]);
    }

    #[test]
    fn test_docopt_protobuf_add_schema() {
        let a = parse_args(&["rq", "-l", "info", "protobuf", "add", "schema.proto"]);
        assert_eq!(a.flag_log, Some("info".to_owned()));
        assert!(a.cmd_protobuf);
        assert!(a.cmd_add);
        assert_eq!(a.arg_schema, "schema.proto");
    }
}
<commit_msg>Improve --help somewhat<commit_after>#![feature(plugin)]
#![plugin(docopt_macros)]

extern crate ansi_term;
extern crate docopt;
extern crate duk;
extern crate env_logger;
#[macro_use]
extern crate log;
extern crate nix;
extern crate protobuf;
extern crate record_query;
extern crate rustc_serialize;
extern crate serde_protobuf;

use std::env;
use std::io;
use std::path;

use record_query as rq;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");

docopt!(pub Args derive Debug, concat!("
rq - record query v", env!("CARGO_PKG_VERSION"), "

A tool for manipulating data records.

Records are read from stdin, processed, and written to stdout.  The tool accepts
a query in the custom rq query language as its main command-line arguments.

See https://github.com/dflemstr/rq for in-depth documentation.

Usage:
  rq (--help|--version)
  rq [-j|-c|-p <type>] [-J|-C|-P <type>] [-l <spec>|-q] [--trace] [--] [<query>]
  rq [-l <spec>|-q] [--trace] protobuf add <schema> [--base <path>]

Options:
  --help
      Show this screen.
  --version
      Show the program name and version.

  -j, --input-json
      Input is white-space separated JSON values.
  -J, --output-json
      Output should be formatted as JSON values.
  -c, --input-cbor
      Input is a series of CBOR values.
  -C, --output-cbor
      Output is a series of CBOR values.
  -p <type>, --input-protobuf <type>
      Input is a single protocol buffer object.  The argument refers to the
      fully qualified name of the message type (including the leading '.').
  -P <type>, --output-protobuf <type>
      Output should be formatted as protocol buffer objects.  The argument
      refers to the fully qualified name of the message type (including the
      leading '.').

  <query>
      A query indicating how to transform each record.

  --base <path>
      Directories are significant when dealing with protocol buffer
      schemas.  This specifies the base directory used to normalize schema
      file paths [default: .]

  -l <spec>, --log <spec>
      Configure logging using the supplied specification, in the format of
      `env_logger`, for example `rq=info,duk=trace/[0-9]+ objects kept`.
      See: https://doc.rust-lang.org/log/env_logger
  --trace
      Enable (back)trace output on error.
  -q, --quiet
      Log nothing.
"),
        flag_input_protobuf: Option<String>,
        flag_output_protobuf: Option<String>,
        flag_log: Option<String>,
        flag_base: Option<String>);

fn main() {
    let args: Args = Args::docopt()
                         .version(Some(VERSION.to_owned()))
                         .decode()
                         .unwrap_or_else(|e| e.exit());

    setup_log(args.flag_log.as_ref().map(String::as_ref), args.flag_quiet);

    main_with_args(&args).unwrap_or_else(|e| log_error(&args, e));
}

fn main_with_args(args: &Args) -> rq::error::Result<()> {
    let paths = rq::config::Paths::new().unwrap();

    if args.cmd_protobuf {
        if args.cmd_add {
            let schema = path::Path::new(&args.arg_schema);
            let base = path::Path::new(if let Some(ref b) = args.flag_base {
                b.as_str()
            } else {
                "."
            });
            rq::proto_index::add_file(&paths, base, schema)
        } else {
            unreachable!()
        }
    } else {
        run(&args, &paths)
    }
}

fn run(args: &Args, paths: &rq::config::Paths) -> rq::error::Result<()> {

    let stdin = io::stdin();
    let mut input = stdin.lock();

    if let Some(ref name) = args.flag_input_protobuf {
        let proto_descriptors = try!(load_descriptors(&paths));
        let stream = protobuf::CodedInputStream::new(&mut input);
        let source = try!(rq::value::protobuf::source(&proto_descriptors, name, stream));
        run_source(args, paths, source)
    } else if args.flag_input_cbor {
        let source = rq::value::cbor::source(&mut input);
        run_source(args, paths, source)
    } else {
        let source = rq::value::json::source(&mut input);
        run_source(args, paths, source)
    }
}

fn run_source<I>(args: &Args, paths: &rq::config::Paths, source: I) -> rq::error::Result<()>
    where I: rq::value::Source
{
    let mut output = io::stdout();

    if let Some(_) = args.flag_output_protobuf {
        Err(rq::error::Error::unimplemented("protobuf serialization".to_owned()))
    } else if args.flag_output_cbor {
        let sink = rq::value::cbor::sink(&mut output);
        run_source_sink(args, paths, source, sink)
    } else {
        let sink = rq::value::json::sink(&mut output);
        run_source_sink(args, paths, source, sink)
    }
}

fn run_source_sink<I, O>(args: &Args,
                         _paths: &rq::config::Paths,
                         source: I,
                         mut sink: O)
                         -> rq::error::Result<()>
    where I: rq::value::Source,
          O: rq::value::Sink
{
    use record_query::value::Source;

    let query = rq::query::Query::parse(if args.arg_query.is_empty() {
        "id"
    } else {
        &args.arg_query
    });
    let query_context = rq::query::Context::new();
    let mut results = try!(query.evaluate(&query_context, source));
    while let Some(result) = try!(results.read()) {
        try!(sink.write(result));
    }
    Ok(())
}

fn load_descriptors(paths: &rq::config::Paths)
                    -> rq::error::Result<serde_protobuf::descriptor::Descriptors> {
    let descriptors_proto = try!(rq::proto_index::compile_descriptor_set(paths));
    Ok(serde_protobuf::descriptor::Descriptors::from_proto(&descriptors_proto))
}

fn log_error(args: &Args, error: rq::error::Error) {
    use record_query::error::ErrorKind;

    match *error.kind() {
        ErrorKind::Msg(ref m) => error!("{}", m),
        ErrorKind::Duk(duk::ErrorKind::Js(ref e)) => {
            if let Some(ref stack) = e.stack {
                error!("Error while executing Javascript:");
                for line in stack.lines() {
                    error!("{}", line);
                }
            } else {
                error!("Error while executing Javascript: {}", e.message);
            }
        },
        _ => {
            error!("Encountered: {}", error);
            for e in error.iter().skip(1) {
                error!("Caused by: {}", e);
            }
        },
    }

    if args.flag_trace || env::var("RUST_BACKTRACE").as_ref().map(String::as_str) == Ok("1") {
        error!("");
        error!("Backtrace:");
        for line in format!("{:?}", error.backtrace()).lines() {
            error!("  {}", line);
        }
    } else {
        error!("(Re-run with (-t|--trace) or RUST_BACKTRACE=1 for a backtrace)");
    }
}

fn setup_log(spec: Option<&str>, quiet: bool) {
    use log::LogLevelFilter;

    let mut builder = env_logger::LogBuilder::new();

    if quiet {
        builder.filter(None, LogLevelFilter::Off);
    } else if let Some(s) = spec {
        builder.parse(s);
    } else if let Ok(s) = env::var("RUST_LOG") {
        builder.parse(&s);
    } else {
        builder.filter(None, LogLevelFilter::Info);
    };

    builder.format(format_log_record);

    builder.init().unwrap();
}

fn format_log_record(record: &log::LogRecord) -> String {
    use ansi_term::ANSIStrings;
    use ansi_term::Colour;
    use ansi_term::Style;
    use log::LogLevel;
    use nix::unistd;
    use nix::sys::ioctl;

    if unistd::isatty(ioctl::libc::STDERR_FILENO).unwrap_or(false) {
        let normal = Style::new();
        let (front, back) = match record.level() {
            LogLevel::Error => (Colour::Red.normal(), Colour::Red.dimmed()),
            LogLevel::Warn => (Colour::Yellow.normal(), Colour::Yellow.dimmed()),
            LogLevel::Info => (Colour::Blue.normal(), Colour::Blue.dimmed()),
            LogLevel::Debug => (Colour::Purple.normal(), Colour::Purple.dimmed()),
            LogLevel::Trace => (Colour::White.dimmed(), Colour::Black.normal()),
        };

        let strings = &[back.paint("["),
                        front.paint(format!("{}", record.level())),
                        back.paint("]"),
                        normal.paint(" "),
                        back.paint("["),
                        front.paint(record.location().module_path()),
                        back.paint("]"),
                        normal.paint(" "),
                        back.paint(format!("{}", record.args()))];

        format!("{}", ANSIStrings(strings))
    } else {
        format!("[{}] [{}] {}",
                record.level(),
                record.location().module_path(),
                record.args())
    }

}

#[cfg(test)]
mod test {
    use super::*;

    fn parse_args(args: &[&str]) -> Args {
        let a = Args::docopt().argv(args).decode().unwrap();
        println!("{:?}", a);
        a
    }

    #[test]
    fn test_docopt_kitchen_sink() {
        let a = parse_args(&["rq", "-l", "info", "-jP", ".foo.Bar", "select x"]);
        assert!(a.flag_input_json);
        assert_eq!(a.flag_output_protobuf, Some(".foo.Bar".to_owned()));
        assert_eq!(a.flag_log, Some("info".to_owned()));
        assert_eq!(a.arg_query, "select x");
    }

    #[test]
    fn test_docopt_no_args() {
        parse_args(&["rq"]);
    }

    #[test]
    #[should_panic(expected = "Help")]
    fn test_docopt_help() {
        parse_args(&["rq", "--help"]);
    }

    #[test]
    fn test_docopt_version() {
        let a = parse_args(&["rq", "--version"]);
        assert!(a.flag_version);
    }

    #[test]
    fn test_docopt_input_json() {
        let a = parse_args(&["rq", "-j"]);
        assert!(a.flag_input_json);
    }

    #[test]
    fn test_docopt_input_json_long() {
        let a = parse_args(&["rq", "--input-json"]);
        assert!(a.flag_input_json);
    }

    #[test]
    fn test_docopt_output_json() {
        let a = parse_args(&["rq", "-J"]);
        assert!(a.flag_output_json);
    }

    #[test]
    fn test_docopt_output_json_long() {
        let a = parse_args(&["rq", "--output-json"]);
        assert!(a.flag_output_json);
    }

    #[test]
    fn test_docopt_input_cbor() {
        let a = parse_args(&["rq", "-c"]);
        assert!(a.flag_input_cbor);
    }

    #[test]
    fn test_docopt_input_cbor_long() {
        let a = parse_args(&["rq", "--input-cbor"]);
        assert!(a.flag_input_cbor);
    }

    #[test]
    fn test_docopt_output_cbor() {
        let a = parse_args(&["rq", "-C"]);
        assert!(a.flag_output_cbor);
    }

    #[test]
    fn test_docopt_output_cbor_long() {
        let a = parse_args(&["rq", "--output-cbor"]);
        assert!(a.flag_output_cbor);
    }

    #[test]
    fn test_docopt_input_protobuf() {
        let a = parse_args(&["rq", "-p", ".foo.Bar"]);
        assert_eq!(a.flag_input_protobuf, Some(".foo.Bar".to_owned()));
    }

    #[test]
    fn test_docopt_input_protobuf_long() {
        let a = parse_args(&["rq", "--input-protobuf", ".foo.Bar"]);
        assert_eq!(a.flag_input_protobuf, Some(".foo.Bar".to_owned()));
    }

    #[test]
    fn test_docopt_output_protobuf() {
        let a = parse_args(&["rq", "-P", ".foo.Bar"]);
        assert_eq!(a.flag_output_protobuf, Some(".foo.Bar".to_owned()));
    }

    #[test]
    fn test_docopt_output_protobuf_long() {
        let a = parse_args(&["rq", "--output-protobuf", ".foo.Bar"]);
        assert_eq!(a.flag_output_protobuf, Some(".foo.Bar".to_owned()));
    }

    #[test]
    #[should_panic(expected = "NoMatch")]
    fn test_docopt_input_conflict() {
        parse_args(&["rq", "-jc"]);
    }

    #[test]
    #[should_panic(expected = "NoMatch")]
    fn test_docopt_output_conflict() {
        parse_args(&["rq", "-JC"]);
    }

    #[test]
    fn test_docopt_protobuf_add_schema() {
        let a = parse_args(&["rq", "-l", "info", "protobuf", "add", "schema.proto"]);
        assert_eq!(a.flag_log, Some("info".to_owned()));
        assert!(a.cmd_protobuf);
        assert!(a.cmd_add);
        assert_eq!(a.arg_schema, "schema.proto");
    }
}
<|endoftext|>


<commit_before>extern crate reqwest;
extern crate serde;
extern crate serde_json;
extern crate serde_with;
extern crate url;

use reqwest::header::{Authorization, Bearer, Range};
use url::Url;

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Bucket {
    id: String,
    name: String,
    location: String,
    self_link: String,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Object {
    id: String,
    name: String,
    bucket: String,
    self_link: String,
    #[serde(with = "serde_with::rust::display_fromstr")]
    size: u64,
}

fn new_client() -> Result<reqwest::Client, reqwest::Error> {
    // NOTE(boulos): The service account needs both storage viewer (to see objects) and *project* viewer to see the Bucket.
    let creds = Bearer {
        token: "IMABADPERSON_HARDCODED_HERE_gcloud_auth_print_access_token_for_the_service_account".to_owned()
    };

    let mut headers = reqwest::header::Headers::new();
    headers.set(Authorization(creds));

    return reqwest::Client::builder()
        .default_headers(headers)
        .build();
}

fn get_bucket(url: Url) -> Result<Bucket, reqwest::Error> {
    println!("Looking to request: {:#?}", url);

    let client = new_client()?;

    let mut response = client.get(url)
        .send()
        .expect("Failed to send request");

    println!("{:#?}", response);

    let bucket = response.json::<Bucket>();
    println!("{:#?}", bucket);
    return bucket
}

fn get_object(url: Url) -> Result<Object, reqwest::Error> {
    println!("Looking to request: {:#?}", url);

    let client = new_client()?;

    let mut response = client.get(url)
        .send()
        .expect("Failed to send request");

    println!("{:#?}", response);

    let object = response.json::<Object>();
    println!("{:#?}", object);
    return object
}

fn get_bytes(obj: &Object, offset: u64, how_many: u64) -> Result<Vec<u8>, reqwest::Error> {
    println!("Asking for {} bytes at {} from the origin for {}", how_many, offset, obj.name);

    // Use the self_link from the object as the url, but add ?alt=media
    let mut object_url = Url::parse(&obj.self_link).unwrap();
    object_url.query_pairs_mut().append_pair("alt", "media");

    let client = new_client()?;

    let mut response = client.get(object_url)
        .header(Range::bytes(offset, offset + how_many))
        .send()
        .expect("Failed to send request");

    let mut buf: Vec<u8> = vec![];
    let written = response.copy_to(&mut buf)?;
    println!("Got back {} bytes", written);
    Ok(buf)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn get_landsat() {
        let landsat_url = Url::parse(
            "https://www.googleapis.com/storage/v1/b/gcp-public-data-landsat"
        ).unwrap();
        get_bucket(landsat_url);
    }

    #[test]
    fn get_private_bucket() {
        let bucket_url = Url::parse(
            "https://www.googleapis.com/storage/v1/b/boulos-vm-ml"
        ).unwrap();
        get_bucket(bucket_url);
    }

    #[test]
    fn get_private_object() {
        let object_url = Url::parse(
            "https://www.googleapis.com/storage/v1/b/boulos-hadoop/o/bdutil-staging%2fhadoop-m%2f20150202-172447-58j%2fbq-mapred-template.xml"
        ).unwrap();
        let object: Object = get_object(object_url).unwrap();
        println!("Object has {} bytes", object.size);

        let bytes: Vec<u8> = get_bytes(&object, 0, 769).unwrap();
        println!("Got back:\n {}", String::from_utf8(bytes).unwrap());

        let offset_bytes: Vec<u8> = get_bytes(&object, 6, 769).unwrap();
        println!("Got back:\n {}", String::from_utf8(offset_bytes).unwrap());

    }
}
<commit_msg>Add support for list objects with pagination.<commit_after>extern crate reqwest;
extern crate serde;
extern crate serde_json;
extern crate serde_with;
extern crate url;

use reqwest::header::{Authorization, Bearer, Range};
use url::Url;

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Bucket {
    id: String,
    name: String,
    location: String,
    self_link: String,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Object {
    id: String,
    name: String,
    bucket: String,
    self_link: String,
    #[serde(with = "serde_with::rust::display_fromstr")]
    size: u64,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ListObjectsResponse {
    next_page_token: Option<String>,
    items: Vec<Object>,
}

fn new_client() -> Result<reqwest::Client, reqwest::Error> {
    // NOTE(boulos): The service account needs both storage viewer (to see objects) and *project* viewer to see the Bucket.
    let creds = Bearer {
        token: "IMABADPERSON_HARDCODED_HERE_gcloud_auth_print_access_token_for_the_service_account".to_owned()
    };

    let mut headers = reqwest::header::Headers::new();
    headers.set(Authorization(creds));

    return reqwest::Client::builder()
        .default_headers(headers)
        .build();
}

fn get_bucket(url: Url) -> Result<Bucket, reqwest::Error> {
    println!("Looking to request: {:#?}", url);

    let client = new_client()?;

    let mut response = client.get(url)
        .send()
        .expect("Failed to send request");

    println!("{:#?}", response);

    let bucket = response.json::<Bucket>();
    println!("{:#?}", bucket);
    return bucket
}

fn get_object(url: Url) -> Result<Object, reqwest::Error> {
    println!("Looking to request: {:#?}", url);

    let client = new_client()?;

    let mut response = client.get(url)
        .send()
        .expect("Failed to send request");

    println!("{:#?}", response);

    let object = response.json::<Object>();
    println!("{:#?}", object);
    return object
}

fn get_bytes(obj: &Object, offset: u64, how_many: u64) -> Result<Vec<u8>, reqwest::Error> {
    println!("Asking for {} bytes at {} from the origin for {}", how_many, offset, obj.name);

    // Use the self_link from the object as the url, but add ?alt=media
    let mut object_url = Url::parse(&obj.self_link).unwrap();
    object_url.query_pairs_mut().append_pair("alt", "media");

    let client = new_client()?;

    let mut response = client.get(object_url)
        .header(Range::bytes(offset, offset + how_many))
        .send()
        .expect("Failed to send request");

    let mut buf: Vec<u8> = vec![];
    let written = response.copy_to(&mut buf)?;
    println!("Got back {} bytes", written);
    Ok(buf)
}

fn _do_one_list_object(bucket: &str, prefix: Option<&str>, delim: Option<&str>, token: Option<&str>) -> Result<ListObjectsResponse, reqwest::Error> {
    let mut list_url = Url::parse(bucket).unwrap();
    if let Some(prefix_str) = prefix {
        list_url.query_pairs_mut().append_pair("prefix", prefix_str);
    }

    if let Some(delim_str) = delim {
        list_url.query_pairs_mut().append_pair("delimiter", delim_str);
    }

    if let Some(token_str) = token {
        list_url.query_pairs_mut().append_pair("pageToken", token_str);
    }

    let client = new_client()?;

    let mut response = client.get(list_url)
        .send()
        .expect("Failed to send request");

    let list_response = response.json::<ListObjectsResponse>();
    return list_response;
}

fn list_objects(bucket: &str, prefix: Option<&str>, delim: Option<&str>) -> Result<Vec<Object>, reqwest::Error> {
    println!("Asking for a list from bucket '{}' with prefix '{:#?}' and delim = '{:#?}'", bucket, prefix, delim);

    let mut results: Vec<Object> = vec![];

    let mut page_token: String = String::from("");

    loop {
        let mut resp = match page_token.is_empty() {
            true => _do_one_list_object(bucket, prefix, delim, None)?,
            false => _do_one_list_object(bucket, prefix, delim, Some(&page_token))?,
        };

        results.append(&mut resp.items);

        match resp.next_page_token {
            Some(temp_token_str) => page_token = temp_token_str.clone(),
            None => break,
        }
    }

    Ok(results)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn get_landsat() {
        let landsat_url = Url::parse(
            "https://www.googleapis.com/storage/v1/b/gcp-public-data-landsat"
        ).unwrap();
        get_bucket(landsat_url);
    }

    #[test]
    fn get_private_bucket() {
        let bucket_url = Url::parse(
            "https://www.googleapis.com/storage/v1/b/boulos-vm-ml"
        ).unwrap();
        get_bucket(bucket_url);
    }

    #[test]
    fn get_private_object() {
        let object_url = Url::parse(
            "https://www.googleapis.com/storage/v1/b/boulos-hadoop/o/bdutil-staging%2fhadoop-m%2f20150202-172447-58j%2fbq-mapred-template.xml"
        ).unwrap();
        let object: Object = get_object(object_url).unwrap();
        println!("Object has {} bytes", object.size);

        let bytes: Vec<u8> = get_bytes(&object, 0, 769).unwrap();
        println!("Got back:\n {}", String::from_utf8(bytes).unwrap());

        let offset_bytes: Vec<u8> = get_bytes(&object, 6, 769).unwrap();
        println!("Got back:\n {}", String::from_utf8(offset_bytes).unwrap());

    }

    #[test]
    fn test_list_objects() {
        let object_url = "https://www.googleapis.com/storage/v1/b/boulos-hadoop/o";
        let prefix = "bdutil-staging";
        let delim = "%2F";

        let objects: Vec<Object> = list_objects(object_url, Some(prefix), Some(delim)).unwrap();
        println!("Got {} objects", objects.len());
        println!("Dump:\n\n{:#?}", objects);

        let all_objects: Vec<Object> = list_objects(object_url, None, None).unwrap();
        println!("Got {} objects", all_objects.len());
        println!("Dump:\n\n{:#?}", all_objects);

        let only_top_level: Vec<Object> = list_objects(object_url, None, Some(delim)).unwrap();
        println!("Got {} objects", only_top_level.len());
        println!("Dump:\n\n{:#?}", only_top_level);
    }

    #[test]
    fn test_list_paginated() {
        let object_url = "https://www.googleapis.com/storage/v1/b/gcp-public-data-landsat/o";
        let prefix = "LC08/PRE/044/034/";
        let delim = "%2F";

        let objects: Vec<Object> = list_objects(object_url, Some(prefix), Some(delim)).unwrap();
        println!("Got {} objects", objects.len());
    }
}
<|endoftext|>


<commit_before>use std::mem::swap;
use std::ops::Deref;

use unicode_width::UnicodeWidthStr;

use strcursor::StrCursor;

pub struct Buffer {
    front_buf: String,
    back_buf: String,
    pos: usize
}

impl Buffer {

    pub fn new() -> Buffer {
        Buffer {
            front_buf: String::new(),
            back_buf: String::new(),
            pos: 0
        }
    }

    pub fn swap(&mut self) {
        swap(&mut self.front_buf, &mut self.back_buf);
        self.pos = self.front_buf.len();
    }

    pub fn reset(&mut self) {
        self.front_buf.clear();
        self.pos = 0;
    }

    pub fn replace(&mut self, s: &str) {
        self.front_buf.clear();
        self.front_buf.extend(s.chars());
        self.pos = s.len();
    }

    pub fn insert_char_at_cursor(&mut self, c: char) {
        self.front_buf.insert(self.pos, c);
        self.pos += c.len_utf8();
    }

    pub fn insert_chars_at_cursor(&mut self, s: String) {
        for c in s.chars() {
            self.insert_char_at_cursor(c);
        }
    }

    pub fn delete_char_left_of_cursor(&mut self) {
        if self.move_left() {
            self.front_buf.remove(self.pos);
        }
    }

    pub fn delete_char_right_of_cursor(&mut self) -> bool {
        if self.pos < self.front_buf.len() {
            self.front_buf.remove(self.pos);
            return true;
        } else {
            return false;
        }
    }

    fn cursor(&self) -> StrCursor {
        StrCursor::new_at_left_of_byte_pos(self.front_buf.deref(), self.pos)
    }

    fn prev_pos(&self) -> Option<usize> {
        self.cursor().at_prev().map(|c| c.byte_pos())
    }

    pub fn move_left(&mut self) -> bool {
        match self.prev_pos() {
            Some(pos) => {
                self.pos = pos;
                true
            },
            None => false
        }
    }

    fn next_pos(&self) -> Option<usize> {
        self.cursor().at_next().map(|c| c.byte_pos())
    }

    pub fn move_right(&mut self) -> bool {
        match self.next_pos() {
            Some(pos) => {
                self.pos = pos;
                true
            },
            None => false
        }
    }

    pub fn move_start(&mut self) {
        self.pos = 0;
    }

    pub fn move_end(&mut self) {
        self.pos = self.front_buf.len();
    }

    fn char_pos(&self) -> usize {
         self.cursor().slice_before().width()
    }

    pub fn get_line(&self, prompt: &str) -> Vec<u8> {
        let mut seq = Vec::new();
        seq.extend("\r".as_bytes());
        seq.extend(prompt.as_bytes());
        seq.extend(self.front_buf.as_bytes());
        seq.extend("\x1b[0K".as_bytes());
        seq.extend(&format!("\r\x1b[{}C", prompt.len() + self.char_pos()).into_bytes());
        seq
    }

    pub fn to_string(self) -> String {
        self.front_buf
    }

}
<commit_msg>fix unicode width<commit_after>use std::mem::swap;
use std::ops::Deref;

use unicode_width::UnicodeWidthStr;

use strcursor::StrCursor;

pub struct Buffer {
    front_buf: String,
    back_buf: String,
    pos: usize
}

impl Buffer {

    pub fn new() -> Buffer {
        Buffer {
            front_buf: String::new(),
            back_buf: String::new(),
            pos: 0
        }
    }

    pub fn swap(&mut self) {
        swap(&mut self.front_buf, &mut self.back_buf);
        self.pos = self.front_buf.len();
    }

    pub fn reset(&mut self) {
        self.front_buf.clear();
        self.pos = 0;
    }

    pub fn replace(&mut self, s: &str) {
        self.front_buf.clear();
        self.front_buf.extend(s.chars());
        self.pos = s.len();
    }

    pub fn insert_char_at_cursor(&mut self, c: char) {
        self.front_buf.insert(self.pos, c);
        self.pos += c.len_utf8();
    }

    pub fn insert_chars_at_cursor(&mut self, s: String) {
        for c in s.chars() {
            self.insert_char_at_cursor(c);
        }
    }

    pub fn delete_char_left_of_cursor(&mut self) {
        if self.move_left() {
            self.front_buf.remove(self.pos);
        }
    }

    pub fn delete_char_right_of_cursor(&mut self) -> bool {
        if self.pos < self.front_buf.len() {
            self.front_buf.remove(self.pos);
            return true;
        } else {
            return false;
        }
    }

    fn cursor(&self) -> StrCursor {
        StrCursor::new_at_left_of_byte_pos(self.front_buf.deref(), self.pos)
    }

    fn prev_pos(&self) -> Option<usize> {
        self.cursor().at_prev().map(|c| c.byte_pos())
    }

    pub fn move_left(&mut self) -> bool {
        match self.prev_pos() {
            Some(pos) => {
                self.pos = pos;
                true
            },
            None => false
        }
    }

    fn next_pos(&self) -> Option<usize> {
        self.cursor().at_next().map(|c| c.byte_pos())
    }

    pub fn move_right(&mut self) -> bool {
        match self.next_pos() {
            Some(pos) => {
                self.pos = pos;
                true
            },
            None => false
        }
    }

    pub fn move_start(&mut self) {
        self.pos = 0;
    }

    pub fn move_end(&mut self) {
        self.pos = self.front_buf.len();
    }

    fn char_pos(&self) -> usize {
         UnicodeWidthStr::width(self.cursor().slice_before())
    }

    pub fn get_line(&self, prompt: &str) -> Vec<u8> {
        let mut seq = Vec::new();
        seq.extend("\r".as_bytes());
        seq.extend(prompt.as_bytes());
        seq.extend(self.front_buf.as_bytes());
        seq.extend("\x1b[0K".as_bytes());
        seq.extend(&format!("\r\x1b[{}C", prompt.len() + self.char_pos()).into_bytes());
        seq
    }

    pub fn to_string(self) -> String {
        self.front_buf
    }

}
<|endoftext|>


<commit_before>use std::io::{File, BufferedReader};
use std::cell::RefCell;

use utils;
use cursor::{Direction, Cursor};

pub struct Buffer {
    pub file_path: String,
    pub lines: Vec<RefCell<Line>>,

    pub cursor: Cursor,
}

impl Buffer {
    /// Create a new buffer instance
    pub fn new() -> Buffer {
        Buffer {
            file_path: String::new(),
            lines: Vec::new(),
            cursor: Cursor::new(),
        }
    }

    /// Create a new buffer instance and load the given file
    pub fn new_from_file(path: &Path) -> Buffer {
        let mut file = BufferedReader::new(File::open(path));
        let lines: Vec<String> = file.lines().map(|x| x.unwrap()).collect();
        let mut buffer = Buffer::new();

        buffer.file_path = path.as_str().unwrap().to_string();

        // for every line in the file we add a corresponding line to the buffer
        for line in lines.iter() {
            let mut data = line.clone();
            // remove \n chars
            data.pop();
            buffer.lines.push(RefCell::new(Line::new(data)));
        }

        buffer
    }

    /// Draw the contents of the buffer
    ///
    /// Loops over each line in the buffer and draws it to the screen
    pub fn draw_contents(&self) {
        for (index, line) in self.lines.iter().enumerate() {
            let ln = line.borrow();
            utils::draw(index, ln.data.clone());
        }
    }

    pub fn draw_status(&self) {
        let height = utils::get_term_height();
        let (cursor_x, cursor_y) = self.cursor.get_position();
        let data = self.file_path.clone();
        let line_count = self.lines.len();
        utils::draw(
            height - 1,
            format!("{}, cursor: {}-{}, termwidth: {}, termheight: {}, lines: {}",
                    data, cursor_x, cursor_y, utils::get_term_height(), utils::get_term_width(), line_count));
    }

    pub fn adjust_cursor(&mut self, dir: Direction) {
        let (mut x, mut y) = self.cursor.get_position();
        match dir {
            Direction::Up => {
                let line = self.get_line_at(y-1);
                if line.is_some() {
                    y -= 1;
                    // if the current cursor offset is after the end of the
                    // previous line, move the offset back to the end of the line
                    let line_len = line.unwrap().borrow().data.len();
                    if x > line_len {
                        x = line_len;
                    }
                }
            }
            Direction::Down => {
                let line = self.get_line_at(y+1);
                if line.is_some() {
                    y += 1;
                    // if the current cursor offset is after the end of the
                    // next line, move the offset back to the end of the line
                    let line_len = line.unwrap().borrow().data.len();
                    if x > line_len {
                        x = line_len;
                    }
                }
            }
            Direction::Right => {
                let line = &self.get_line_at(y);
                if line.is_some() && line.unwrap().borrow().len() > x {
                    x += 1
                }
            }
            Direction::Left => {
                let line = &self.get_line_at(y);
                if line.is_some() && x > 0 {
                    x -= 1
                }
            }
        }
        self.cursor.set_position(x, y);
    }

    pub fn delete_char(&mut self) {
        let (offset, line_num) = self.cursor.get_position();

        if offset == 0 {
            return self.join_line_with_previous(line_num);
        }

        {
            let line = self.get_line_at(line_num);
            line.unwrap().borrow_mut().data.remove(offset);
        }
        self.cursor.set_position(offset - 1, line_num);
    }

    pub fn insert_char(&mut self, ch: char) {
       let (mut x, y) = self.cursor.get_position();
       {
           let line = &self.get_line_at(y);

           // get Vec<u8> from the current line contents
           let mut data = line.unwrap().borrow().data.clone().into_bytes();

           // add the new character to the Vec at the cursors `x` position
           data.insert(x, ch as u8);

           // convert to Vec back into a string
           let new_data = String::from_utf8(data);

           if new_data.is_ok() {
               // update the line
               line.unwrap().borrow_mut().data = new_data.unwrap();
           }
           x += 1;
       }
       self.cursor.set_position(x, y);

    }

    pub fn insert_new_line(&mut self) {
        let line_num = self.cursor.get_linenum();

        // split the current line at the cursor position
        let bits = &self.split_line();
        self.update_line(bits.clone());

        // move the cursor down and to the start of the next line
        self.cursor.set_position(0, line_num + 1);
    }

    /// Join the line identified by `line_num` with the one at `line_num - 1 `.
    fn join_line_with_previous(&mut self, line_num: uint) {
        let mut current_line_data: String;
        let mut prev_line_data: String;
        let line_len: uint;
        {
            let prev_line = self.get_line_at(line_num - 1);
            if prev_line.is_none() {
                return
            }
            prev_line_data = prev_line.unwrap().borrow().data.clone();
            line_len = prev_line_data.len();
        }
        {
            // get current line data
            let current_line = self.get_line_at(line_num);
            current_line_data = current_line.unwrap().borrow().data.clone();
        }
        {
            // append current line data to prev line
            // FIXME: this is duplicated above in a different scope...
            let prev_line = self.get_line_at(line_num - 1).unwrap();

            let new_data = format!("{}{}", prev_line_data, current_line_data);
            prev_line.borrow_mut().data = new_data;
        }

        // clear the line
        utils::clear_line(line_num);
        // remove current line
        self.lines.remove(line_num);
        // move the cursor
        self.cursor.set_position(line_len, line_num - 1);
    }

    fn update_line(&mut self, mut bits: Vec<String>) {
        let line_num = self.cursor.get_linenum();
        {
            // truncate the current line
            let line = &self.get_line_at(line_num);
            line.unwrap().borrow_mut().data = bits.remove(0).unwrap();
        }

        // add new line below current
        utils::clear_line(line_num+1);
        self.lines.insert(line_num+1, RefCell::new(Line::new(bits.remove(0).unwrap())));
    }

    fn split_line(&mut self) -> Vec<String> {
        let (x, y) = self.cursor.get_position();
        let line = &self.get_line_at(y);

        let data = line.unwrap().borrow().data.clone().into_bytes();
        let old_data = data.slice_to(x);
        let new_data = data.slice_from(x);

        vec!(
            String::from_utf8_lossy(old_data).into_string(),
            String::from_utf8_lossy(new_data).into_string(),
        )
    }

    fn get_line_at(&mut self, line_num: uint) -> Option<&RefCell<Line>> {
        for (index, line) in self.lines.iter().enumerate() {
            if index == line_num {
                return Some(line)
            }
        }
        None
    }

}

pub struct Line {
    pub data: String,
}

impl Line {
    /// Create a new line instance
    pub fn new(data: String) -> Line {
        Line{
            data: data,
        }
    }

    /// Get the length of the current line
    pub fn len(&self) -> uint {
        self.data.len()
    }
}

<commit_msg>Fixed character deletion<commit_after>use std::io::{File, BufferedReader};
use std::cell::RefCell;

use utils;
use cursor::{Direction, Cursor};

pub struct Buffer {
    pub file_path: String,
    pub lines: Vec<RefCell<Line>>,

    pub cursor: Cursor,
}

impl Buffer {
    /// Create a new buffer instance
    pub fn new() -> Buffer {
        Buffer {
            file_path: String::new(),
            lines: Vec::new(),
            cursor: Cursor::new(),
        }
    }

    /// Create a new buffer instance and load the given file
    pub fn new_from_file(path: &Path) -> Buffer {
        let mut file = BufferedReader::new(File::open(path));
        let lines: Vec<String> = file.lines().map(|x| x.unwrap()).collect();
        let mut buffer = Buffer::new();

        buffer.file_path = path.as_str().unwrap().to_string();

        // for every line in the file we add a corresponding line to the buffer
        for line in lines.iter() {
            let mut data = line.clone();
            // remove \n chars
            data.pop();
            buffer.lines.push(RefCell::new(Line::new(data)));
        }

        buffer
    }

    /// Draw the contents of the buffer
    ///
    /// Loops over each line in the buffer and draws it to the screen
    pub fn draw_contents(&self) {
        for (index, line) in self.lines.iter().enumerate() {
            let ln = line.borrow();
            utils::draw(index, ln.data.clone());
        }
    }

    pub fn draw_status(&self) {
        let height = utils::get_term_height();
        let (cursor_x, cursor_y) = self.cursor.get_position();
        let data = self.file_path.clone();
        let line_count = self.lines.len();
        utils::draw(
            height - 1,
            format!("{}, cursor: {}-{}, termwidth: {}, termheight: {}, lines: {}",
                    data, cursor_x, cursor_y, utils::get_term_height(), utils::get_term_width(), line_count));
    }

    pub fn adjust_cursor(&mut self, dir: Direction) {
        let (mut x, mut y) = self.cursor.get_position();
        match dir {
            Direction::Up => {
                let line = self.get_line_at(y-1);
                if line.is_some() {
                    y -= 1;
                    // if the current cursor offset is after the end of the
                    // previous line, move the offset back to the end of the line
                    let line_len = line.unwrap().borrow().data.len();
                    if x > line_len {
                        x = line_len;
                    }
                }
            }
            Direction::Down => {
                let line = self.get_line_at(y+1);
                if line.is_some() {
                    y += 1;
                    // if the current cursor offset is after the end of the
                    // next line, move the offset back to the end of the line
                    let line_len = line.unwrap().borrow().data.len();
                    if x > line_len {
                        x = line_len;
                    }
                }
            }
            Direction::Right => {
                let line = &self.get_line_at(y);
                if line.is_some() && line.unwrap().borrow().len() > x {
                    x += 1
                }
            }
            Direction::Left => {
                let line = &self.get_line_at(y);
                if line.is_some() && x > 0 {
                    x -= 1
                }
            }
        }
        self.cursor.set_position(x, y);
    }

    pub fn delete_char(&mut self) {
        let (mut offset, line_num) = self.cursor.get_position();

        if offset == 0 {
            return self.join_line_with_previous(line_num);
        }

        offset -= 1;
        {
            let line = self.get_line_at(line_num);
            line.unwrap().borrow_mut().data.remove(offset);
        }
        self.cursor.set_position(offset, line_num);
    }

    pub fn insert_char(&mut self, ch: char) {
       let (mut x, y) = self.cursor.get_position();
       {
           let line = &self.get_line_at(y);

           // get Vec<u8> from the current line contents
           let mut data = line.unwrap().borrow().data.clone().into_bytes();

           // add the new character to the Vec at the cursors `x` position
           data.insert(x, ch as u8);

           // convert to Vec back into a string
           let new_data = String::from_utf8(data);

           if new_data.is_ok() {
               // update the line
               line.unwrap().borrow_mut().data = new_data.unwrap();
           }
           x += 1;
       }
       self.cursor.set_position(x, y);

    }

    pub fn insert_new_line(&mut self) {
        let line_num = self.cursor.get_linenum();

        // split the current line at the cursor position
        let bits = &self.split_line();
        self.update_line(bits.clone());

        // move the cursor down and to the start of the next line
        self.cursor.set_position(0, line_num + 1);
    }

    /// Join the line identified by `line_num` with the one at `line_num - 1 `.
    fn join_line_with_previous(&mut self, line_num: uint) {
        let mut current_line_data: String;
        let mut prev_line_data: String;
        let line_len: uint;
        {
            let prev_line = self.get_line_at(line_num - 1);
            if prev_line.is_none() {
                return
            }
            prev_line_data = prev_line.unwrap().borrow().data.clone();
            line_len = prev_line_data.len();
        }
        {
            // get current line data
            let current_line = self.get_line_at(line_num);
            current_line_data = current_line.unwrap().borrow().data.clone();
        }
        {
            // append current line data to prev line
            // FIXME: this is duplicated above in a different scope...
            let prev_line = self.get_line_at(line_num - 1).unwrap();

            let new_data = format!("{}{}", prev_line_data, current_line_data);
            prev_line.borrow_mut().data = new_data;
        }

        // clear the line
        utils::clear_line(line_num);
        // remove current line
        self.lines.remove(line_num);
        // move the cursor
        self.cursor.set_position(line_len, line_num - 1);
    }

    fn update_line(&mut self, mut bits: Vec<String>) {
        let line_num = self.cursor.get_linenum();
        {
            // truncate the current line
            let line = &self.get_line_at(line_num);
            line.unwrap().borrow_mut().data = bits.remove(0).unwrap();
        }

        // add new line below current
        utils::clear_line(line_num+1);
        self.lines.insert(line_num+1, RefCell::new(Line::new(bits.remove(0).unwrap())));
    }

    fn split_line(&mut self) -> Vec<String> {
        let (x, y) = self.cursor.get_position();
        let line = &self.get_line_at(y);

        let data = line.unwrap().borrow().data.clone().into_bytes();
        let old_data = data.slice_to(x);
        let new_data = data.slice_from(x);

        vec!(
            String::from_utf8_lossy(old_data).into_string(),
            String::from_utf8_lossy(new_data).into_string(),
        )
    }

    fn get_line_at(&mut self, line_num: uint) -> Option<&RefCell<Line>> {
        for (index, line) in self.lines.iter().enumerate() {
            if index == line_num {
                return Some(line)
            }
        }
        None
    }

}

pub struct Line {
    pub data: String,
}

impl Line {
    /// Create a new line instance
    pub fn new(data: String) -> Line {
        Line{
            data: data,
        }
    }

    /// Get the length of the current line
    pub fn len(&self) -> uint {
        self.data.len()
    }
}

<|endoftext|>


<commit_before>//! Commit, Data Change and Rollback Notification Callbacks
#![allow(non_camel_case_types)]

use std::ptr;
use std::os::raw::{c_int, c_char, c_void};

use ffi;

use {Connection, InnerConnection};

/// Authorizer Action Codes
#[derive(Debug, PartialEq)]
pub enum Action {
    UNKNOWN = -1,
    SQLITE_CREATE_INDEX = ffi::SQLITE_CREATE_INDEX as isize,
    SQLITE_CREATE_TABLE = ffi::SQLITE_CREATE_TABLE as isize,
    SQLITE_CREATE_TEMP_INDEX = ffi::SQLITE_CREATE_TEMP_INDEX as isize,
    SQLITE_CREATE_TEMP_TABLE = ffi::SQLITE_CREATE_TEMP_TABLE as isize,
    SQLITE_CREATE_TEMP_TRIGGER = ffi::SQLITE_CREATE_TEMP_TRIGGER as isize,
    SQLITE_CREATE_TEMP_VIEW = ffi::SQLITE_CREATE_TEMP_VIEW as isize,
    SQLITE_CREATE_TRIGGER = ffi::SQLITE_CREATE_TRIGGER as isize,
    SQLITE_CREATE_VIEW = ffi::SQLITE_CREATE_VIEW as isize,
    SQLITE_DELETE = ffi::SQLITE_DELETE as isize,
    SQLITE_DROP_INDEX = ffi::SQLITE_DROP_INDEX as isize,
    SQLITE_DROP_TABLE = ffi::SQLITE_DROP_TABLE as isize,
    SQLITE_DROP_TEMP_INDEX = ffi::SQLITE_DROP_TEMP_INDEX as isize,
    SQLITE_DROP_TEMP_TABLE = ffi::SQLITE_DROP_TEMP_TABLE as isize,
    SQLITE_DROP_TEMP_TRIGGER = ffi::SQLITE_DROP_TEMP_TRIGGER as isize,
    SQLITE_DROP_TEMP_VIEW = ffi::SQLITE_DROP_TEMP_VIEW as isize,
    SQLITE_DROP_TRIGGER = ffi::SQLITE_DROP_TRIGGER as isize,
    SQLITE_DROP_VIEW = ffi::SQLITE_DROP_VIEW as isize,
    SQLITE_INSERT = ffi::SQLITE_INSERT as isize,
    SQLITE_PRAGMA = ffi::SQLITE_PRAGMA as isize,
    SQLITE_READ = ffi::SQLITE_READ as isize,
    SQLITE_SELECT = ffi::SQLITE_SELECT as isize,
    SQLITE_TRANSACTION = ffi::SQLITE_TRANSACTION as isize,
    SQLITE_UPDATE = ffi::SQLITE_UPDATE as isize,
    SQLITE_ATTACH = ffi::SQLITE_ATTACH as isize,
    SQLITE_DETACH = ffi::SQLITE_DETACH as isize,
    SQLITE_ALTER_TABLE = ffi::SQLITE_ALTER_TABLE as isize,
    SQLITE_REINDEX = ffi::SQLITE_REINDEX as isize,
    SQLITE_ANALYZE = ffi::SQLITE_ANALYZE as isize,
    SQLITE_CREATE_VTABLE = ffi::SQLITE_CREATE_VTABLE as isize,
    SQLITE_DROP_VTABLE = ffi::SQLITE_DROP_VTABLE as isize,
    SQLITE_FUNCTION = ffi::SQLITE_FUNCTION as isize,
    SQLITE_SAVEPOINT = ffi::SQLITE_SAVEPOINT as isize,
    SQLITE_COPY = ffi::SQLITE_COPY as isize,
    SQLITE_RECURSIVE = 33,
}

impl From<i32> for Action {
    fn from(code: i32) -> Action {
        match code {
            ffi::SQLITE_CREATE_INDEX => Action::SQLITE_CREATE_INDEX,
            ffi::SQLITE_CREATE_TABLE => Action::SQLITE_CREATE_TABLE,
            ffi::SQLITE_CREATE_TEMP_INDEX => Action::SQLITE_CREATE_TEMP_INDEX,
            ffi::SQLITE_CREATE_TEMP_TABLE => Action::SQLITE_CREATE_TEMP_TABLE,
            ffi::SQLITE_CREATE_TEMP_TRIGGER => Action::SQLITE_CREATE_TEMP_TRIGGER,
            ffi::SQLITE_CREATE_TEMP_VIEW => Action::SQLITE_CREATE_TEMP_VIEW,
            ffi::SQLITE_CREATE_TRIGGER => Action::SQLITE_CREATE_TRIGGER,
            ffi::SQLITE_CREATE_VIEW => Action::SQLITE_CREATE_VIEW,
            ffi::SQLITE_DELETE => Action::SQLITE_DELETE,
            ffi::SQLITE_DROP_INDEX => Action::SQLITE_DROP_INDEX,
            ffi::SQLITE_DROP_TABLE => Action::SQLITE_DROP_TABLE,
            ffi::SQLITE_DROP_TEMP_INDEX => Action::SQLITE_DROP_TEMP_INDEX,
            ffi::SQLITE_DROP_TEMP_TABLE => Action::SQLITE_DROP_TEMP_TABLE,
            ffi::SQLITE_DROP_TEMP_TRIGGER => Action::SQLITE_DROP_TEMP_TRIGGER,
            ffi::SQLITE_DROP_TEMP_VIEW => Action::SQLITE_DROP_TEMP_VIEW,
            ffi::SQLITE_DROP_TRIGGER => Action::SQLITE_DROP_TRIGGER,
            ffi::SQLITE_DROP_VIEW => Action::SQLITE_DROP_VIEW,
            ffi::SQLITE_INSERT => Action::SQLITE_INSERT,
            ffi::SQLITE_PRAGMA => Action::SQLITE_PRAGMA,
            ffi::SQLITE_READ => Action::SQLITE_READ,
            ffi::SQLITE_SELECT => Action::SQLITE_SELECT,
            ffi::SQLITE_TRANSACTION => Action::SQLITE_TRANSACTION,
            ffi::SQLITE_UPDATE => Action::SQLITE_UPDATE,
            ffi::SQLITE_ATTACH => Action::SQLITE_ATTACH,
            ffi::SQLITE_DETACH => Action::SQLITE_DETACH,
            ffi::SQLITE_ALTER_TABLE => Action::SQLITE_ALTER_TABLE,
            ffi::SQLITE_REINDEX => Action::SQLITE_REINDEX,
            ffi::SQLITE_ANALYZE => Action::SQLITE_ANALYZE,
            ffi::SQLITE_CREATE_VTABLE => Action::SQLITE_CREATE_VTABLE,
            ffi::SQLITE_DROP_VTABLE => Action::SQLITE_DROP_VTABLE,
            ffi::SQLITE_FUNCTION => Action::SQLITE_FUNCTION,
            ffi::SQLITE_SAVEPOINT => Action::SQLITE_SAVEPOINT,
            ffi::SQLITE_COPY => Action::SQLITE_COPY,
            33 => Action::SQLITE_RECURSIVE,
            _ => Action::UNKNOWN,
        }
    }
}

impl Connection {
    /// Register a callback function to be invoked whenever a transaction is committed.
    ///
    /// The callback returns `true` to rollback.
    pub fn commit_hook<F>(&self, hook: F)
        where F: FnMut() -> bool
    {
        self.db.borrow_mut().commit_hook(hook);
    }

    /// Register a callback function to be invoked whenever a transaction is committed.
    ///
    /// The callback returns `true` to rollback.
    pub fn rollback_hook<F>(&self, hook: F)
        where F: FnMut()
    {
        self.db.borrow_mut().rollback_hook(hook);
    }

    /// Register a callback function to be invoked whenever a row is updated,
    /// inserted or deleted in a rowid table.
    ///
    /// The callback parameters are:
    ///
    ///   - the type of database update (SQLITE_INSERT, SQLITE_UPDATE or SQLITE_DELETE),
    ///   - the name of the database ("main", "temp", ...),
    ///   - the name of the table that is updated,
    ///   - the ROWID of the row that is updated.
    pub fn update_hook<F>(&self, hook: F)
        where F: FnMut(Action, &str, &str, i64)
    {
        self.db.borrow_mut().update_hook(hook);
    }

    /// Remove hook installed by `update_hook`.
    pub fn remove_update_hook(&self) {
        self.db.borrow_mut().remove_update_hook();
    }

    /// Remove hook installed by `commit_hook`.
    pub fn remove_commit_hook(&self) {
        self.db.borrow_mut().remove_commit_hook();
    }

    /// Remove hook installed by `rollback_hook`.
    pub fn remove_rollback_hook(&self) {
        self.db.borrow_mut().remove_rollback_hook();
    }
}

impl InnerConnection {
    pub fn remove_hooks(&mut self) {
        self.remove_update_hook();
        self.remove_commit_hook();
        self.remove_rollback_hook();
    }

    fn commit_hook<F>(&self, hook: F)
        where F: FnMut() -> bool
    {
        unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
            where F: FnMut() -> bool
        {
            let boxed_hook: *mut F = p_arg as *mut F;
            assert!(!boxed_hook.is_null(),
                    "Internal error - null function pointer");

            if (*boxed_hook)() { 1 } else { 0 }
        }

        let previous_hook = {
            let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
            unsafe {
                ffi::sqlite3_commit_hook(self.db(),
                                         Some(call_boxed_closure::<F>),
                                         boxed_hook as *mut _)
            }
        };
        free_boxed_hook(previous_hook);
    }

    fn rollback_hook<F>(&self, hook: F)
        where F: FnMut()
    {
        unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void)
            where F: FnMut()
        {
            let boxed_hook: *mut F = p_arg as *mut F;
            assert!(!boxed_hook.is_null(),
                    "Internal error - null function pointer");

            (*boxed_hook)();
        }

        let previous_hook = {
            let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
            unsafe {
                ffi::sqlite3_rollback_hook(self.db(),
                                           Some(call_boxed_closure::<F>),
                                           boxed_hook as *mut _)
            }
        };
        free_boxed_hook(previous_hook);
    }

    fn update_hook<F>(&mut self, hook: F)
        where F: FnMut(Action, &str, &str, i64)
    {
        unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void,
                                                   action_code: c_int,
                                                   db_str: *const c_char,
                                                   tbl_str: *const c_char,
                                                   row_id: i64)
            where F: FnMut(Action, &str, &str, i64)
        {
            use std::ffi::CStr;
            use std::str;

            let boxed_hook: *mut F = p_arg as *mut F;
            assert!(!boxed_hook.is_null(),
                    "Internal error - null function pointer");

            let action = Action::from(action_code);
            let db_name = {
                let c_slice = CStr::from_ptr(db_str).to_bytes();
                str::from_utf8_unchecked(c_slice)
            };
            let tbl_name = {
                let c_slice = CStr::from_ptr(tbl_str).to_bytes();
                str::from_utf8_unchecked(c_slice)
            };

            (*boxed_hook)(action, db_name, tbl_name, row_id);
        }

        let previous_hook = {
            let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
            unsafe {
                ffi::sqlite3_update_hook(self.db(),
                                         Some(call_boxed_closure::<F>),
                                         boxed_hook as *mut _)
            }
        };
        free_boxed_hook(previous_hook);
    }

    fn remove_update_hook(&mut self) {
        let previous_hook = unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) };
        free_boxed_hook(previous_hook);
    }

    fn remove_commit_hook(&mut self) {
        let previous_hook = unsafe { ffi::sqlite3_commit_hook(self.db(), None, ptr::null_mut()) };
        free_boxed_hook(previous_hook);
    }

    fn remove_rollback_hook(&mut self) {
        let previous_hook = unsafe { ffi::sqlite3_rollback_hook(self.db(), None, ptr::null_mut()) };
        free_boxed_hook(previous_hook);
    }
}

fn free_boxed_hook(hook: *mut c_void) {
    if !hook.is_null() {
        // TODO make sure that size_of::<*mut F>() is always equal to size_of::<*mut c_void>()
        let _: Box<*mut c_void> = unsafe { Box::from_raw(hook as *mut _) };
    }
}

#[cfg(test)]
mod test {
    use super::Action;
    use Connection;

    #[test]
    fn test_commit_hook() {
        let db = Connection::open_in_memory().unwrap();

        let mut called = false;
        db.commit_hook(|| {
                           called = true;
                           false
                       });
        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
            .unwrap();
        assert!(called);
    }

    #[test]
    fn test_rollback_hook() {
        let db = Connection::open_in_memory().unwrap();

        let mut called = false;
        db.rollback_hook(|| { called = true; });
        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")
            .unwrap();
        assert!(called);
    }

    #[test]
    fn test_update_hook() {
        let db = Connection::open_in_memory().unwrap();

        let mut called = false;
        db.update_hook(|action, db, tbl, row_id| {
                           assert_eq!(Action::SQLITE_INSERT, action);
                           assert_eq!("main", db);
                           assert_eq!("foo", tbl);
                           assert_eq!(1, row_id);
                           called = true;
                       });
        db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
        db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap();
        assert!(called);
    }
}
<commit_msg>Refactor hooks<commit_after>//! Commit, Data Change and Rollback Notification Callbacks
#![allow(non_camel_case_types)]

use std::mem::size_of;
use std::ptr;
use std::os::raw::{c_int, c_char, c_void};

use ffi;

use {Connection, InnerConnection};

/// Authorizer Action Codes
#[derive(Debug, PartialEq)]
pub enum Action {
    UNKNOWN = -1,
    SQLITE_CREATE_INDEX = ffi::SQLITE_CREATE_INDEX as isize,
    SQLITE_CREATE_TABLE = ffi::SQLITE_CREATE_TABLE as isize,
    SQLITE_CREATE_TEMP_INDEX = ffi::SQLITE_CREATE_TEMP_INDEX as isize,
    SQLITE_CREATE_TEMP_TABLE = ffi::SQLITE_CREATE_TEMP_TABLE as isize,
    SQLITE_CREATE_TEMP_TRIGGER = ffi::SQLITE_CREATE_TEMP_TRIGGER as isize,
    SQLITE_CREATE_TEMP_VIEW = ffi::SQLITE_CREATE_TEMP_VIEW as isize,
    SQLITE_CREATE_TRIGGER = ffi::SQLITE_CREATE_TRIGGER as isize,
    SQLITE_CREATE_VIEW = ffi::SQLITE_CREATE_VIEW as isize,
    SQLITE_DELETE = ffi::SQLITE_DELETE as isize,
    SQLITE_DROP_INDEX = ffi::SQLITE_DROP_INDEX as isize,
    SQLITE_DROP_TABLE = ffi::SQLITE_DROP_TABLE as isize,
    SQLITE_DROP_TEMP_INDEX = ffi::SQLITE_DROP_TEMP_INDEX as isize,
    SQLITE_DROP_TEMP_TABLE = ffi::SQLITE_DROP_TEMP_TABLE as isize,
    SQLITE_DROP_TEMP_TRIGGER = ffi::SQLITE_DROP_TEMP_TRIGGER as isize,
    SQLITE_DROP_TEMP_VIEW = ffi::SQLITE_DROP_TEMP_VIEW as isize,
    SQLITE_DROP_TRIGGER = ffi::SQLITE_DROP_TRIGGER as isize,
    SQLITE_DROP_VIEW = ffi::SQLITE_DROP_VIEW as isize,
    SQLITE_INSERT = ffi::SQLITE_INSERT as isize,
    SQLITE_PRAGMA = ffi::SQLITE_PRAGMA as isize,
    SQLITE_READ = ffi::SQLITE_READ as isize,
    SQLITE_SELECT = ffi::SQLITE_SELECT as isize,
    SQLITE_TRANSACTION = ffi::SQLITE_TRANSACTION as isize,
    SQLITE_UPDATE = ffi::SQLITE_UPDATE as isize,
    SQLITE_ATTACH = ffi::SQLITE_ATTACH as isize,
    SQLITE_DETACH = ffi::SQLITE_DETACH as isize,
    SQLITE_ALTER_TABLE = ffi::SQLITE_ALTER_TABLE as isize,
    SQLITE_REINDEX = ffi::SQLITE_REINDEX as isize,
    SQLITE_ANALYZE = ffi::SQLITE_ANALYZE as isize,
    SQLITE_CREATE_VTABLE = ffi::SQLITE_CREATE_VTABLE as isize,
    SQLITE_DROP_VTABLE = ffi::SQLITE_DROP_VTABLE as isize,
    SQLITE_FUNCTION = ffi::SQLITE_FUNCTION as isize,
    SQLITE_SAVEPOINT = ffi::SQLITE_SAVEPOINT as isize,
    SQLITE_COPY = ffi::SQLITE_COPY as isize,
    SQLITE_RECURSIVE = 33,
}

impl From<i32> for Action {
    fn from(code: i32) -> Action {
        match code {
            ffi::SQLITE_CREATE_INDEX => Action::SQLITE_CREATE_INDEX,
            ffi::SQLITE_CREATE_TABLE => Action::SQLITE_CREATE_TABLE,
            ffi::SQLITE_CREATE_TEMP_INDEX => Action::SQLITE_CREATE_TEMP_INDEX,
            ffi::SQLITE_CREATE_TEMP_TABLE => Action::SQLITE_CREATE_TEMP_TABLE,
            ffi::SQLITE_CREATE_TEMP_TRIGGER => Action::SQLITE_CREATE_TEMP_TRIGGER,
            ffi::SQLITE_CREATE_TEMP_VIEW => Action::SQLITE_CREATE_TEMP_VIEW,
            ffi::SQLITE_CREATE_TRIGGER => Action::SQLITE_CREATE_TRIGGER,
            ffi::SQLITE_CREATE_VIEW => Action::SQLITE_CREATE_VIEW,
            ffi::SQLITE_DELETE => Action::SQLITE_DELETE,
            ffi::SQLITE_DROP_INDEX => Action::SQLITE_DROP_INDEX,
            ffi::SQLITE_DROP_TABLE => Action::SQLITE_DROP_TABLE,
            ffi::SQLITE_DROP_TEMP_INDEX => Action::SQLITE_DROP_TEMP_INDEX,
            ffi::SQLITE_DROP_TEMP_TABLE => Action::SQLITE_DROP_TEMP_TABLE,
            ffi::SQLITE_DROP_TEMP_TRIGGER => Action::SQLITE_DROP_TEMP_TRIGGER,
            ffi::SQLITE_DROP_TEMP_VIEW => Action::SQLITE_DROP_TEMP_VIEW,
            ffi::SQLITE_DROP_TRIGGER => Action::SQLITE_DROP_TRIGGER,
            ffi::SQLITE_DROP_VIEW => Action::SQLITE_DROP_VIEW,
            ffi::SQLITE_INSERT => Action::SQLITE_INSERT,
            ffi::SQLITE_PRAGMA => Action::SQLITE_PRAGMA,
            ffi::SQLITE_READ => Action::SQLITE_READ,
            ffi::SQLITE_SELECT => Action::SQLITE_SELECT,
            ffi::SQLITE_TRANSACTION => Action::SQLITE_TRANSACTION,
            ffi::SQLITE_UPDATE => Action::SQLITE_UPDATE,
            ffi::SQLITE_ATTACH => Action::SQLITE_ATTACH,
            ffi::SQLITE_DETACH => Action::SQLITE_DETACH,
            ffi::SQLITE_ALTER_TABLE => Action::SQLITE_ALTER_TABLE,
            ffi::SQLITE_REINDEX => Action::SQLITE_REINDEX,
            ffi::SQLITE_ANALYZE => Action::SQLITE_ANALYZE,
            ffi::SQLITE_CREATE_VTABLE => Action::SQLITE_CREATE_VTABLE,
            ffi::SQLITE_DROP_VTABLE => Action::SQLITE_DROP_VTABLE,
            ffi::SQLITE_FUNCTION => Action::SQLITE_FUNCTION,
            ffi::SQLITE_SAVEPOINT => Action::SQLITE_SAVEPOINT,
            ffi::SQLITE_COPY => Action::SQLITE_COPY,
            33 => Action::SQLITE_RECURSIVE,
            _ => Action::UNKNOWN,
        }
    }
}

impl Connection {
    /// Register a callback function to be invoked whenever a transaction is committed.
    ///
    /// The callback returns `true` to rollback.
    pub fn commit_hook<F>(&self, hook: Option<F>)
    where
        F: FnMut() -> bool,
    {
        self.db.borrow_mut().commit_hook(hook);
    }

    /// Register a callback function to be invoked whenever a transaction is committed.
    ///
    /// The callback returns `true` to rollback.
    pub fn rollback_hook<F>(&self, hook: Option<F>)
    where
        F: FnMut(),
    {
        self.db.borrow_mut().rollback_hook(hook);
    }

    /// Register a callback function to be invoked whenever a row is updated,
    /// inserted or deleted in a rowid table.
    ///
    /// The callback parameters are:
    ///
    ///   - the type of database update (SQLITE_INSERT, SQLITE_UPDATE or SQLITE_DELETE),
    ///   - the name of the database ("main", "temp", ...),
    ///   - the name of the table that is updated,
    ///   - the ROWID of the row that is updated.
    pub fn update_hook<F>(&self, hook: Option<F>)
    where
        F: FnMut(Action, &str, &str, i64),
    {
        self.db.borrow_mut().update_hook(hook);
    }
}

impl InnerConnection {
    pub fn remove_hooks(&mut self) {
        self.update_hook(None::<fn(Action, &str, &str, i64)>);
        self.commit_hook(None::<fn() -> bool>);
        self.rollback_hook(None::<fn()>);
    }

    fn commit_hook<F>(&self, hook: Option<F>)
    where
        F: FnMut() -> bool,
    {
        assert_eq!(size_of::<*mut F>(), size_of::<*mut c_void>());

        unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
        where
            F: FnMut() -> bool,
        {
            let boxed_hook: *mut F = p_arg as *mut F;
            if (*boxed_hook)() {
                1
            } else {
                0
            }
        }

        let previous_hook = match hook {
            Some(hook) => {
                let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
                unsafe {
                    ffi::sqlite3_commit_hook(
                        self.db(),
                        Some(call_boxed_closure::<F>),
                        boxed_hook as *mut _,
                    )
                }
            }
            _ => unsafe { ffi::sqlite3_commit_hook(self.db(), None, ptr::null_mut()) },
        };
        free_boxed_hook(previous_hook);
    }

    fn rollback_hook<F>(&self, hook: Option<F>)
    where
        F: FnMut(),
    {
        assert_eq!(size_of::<*mut F>(), size_of::<*mut c_void>());

        unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void)
        where
            F: FnMut(),
        {
            let boxed_hook: *mut F = p_arg as *mut F;
            (*boxed_hook)();
        }

        let previous_hook = match hook {
            Some(hook) => {
                let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
                unsafe {
                    ffi::sqlite3_rollback_hook(
                        self.db(),
                        Some(call_boxed_closure::<F>),
                        boxed_hook as *mut _,
                    )
                }
            }
            _ => unsafe { ffi::sqlite3_rollback_hook(self.db(), None, ptr::null_mut()) },
        };
        free_boxed_hook(previous_hook);
    }

    fn update_hook<F>(&mut self, hook: Option<F>)
    where
        F: FnMut(Action, &str, &str, i64),
    {
        assert_eq!(size_of::<*mut F>(), size_of::<*mut c_void>());

        unsafe extern "C" fn call_boxed_closure<F>(
            p_arg: *mut c_void,
            action_code: c_int,
            db_str: *const c_char,
            tbl_str: *const c_char,
            row_id: i64,
        ) where
            F: FnMut(Action, &str, &str, i64),
        {
            use std::ffi::CStr;
            use std::str;

            let boxed_hook: *mut F = p_arg as *mut F;

            let action = Action::from(action_code);
            let db_name = {
                let c_slice = CStr::from_ptr(db_str).to_bytes();
                str::from_utf8_unchecked(c_slice)
            };
            let tbl_name = {
                let c_slice = CStr::from_ptr(tbl_str).to_bytes();
                str::from_utf8_unchecked(c_slice)
            };

            (*boxed_hook)(action, db_name, tbl_name, row_id);
        }

        let previous_hook = match hook {
            Some(hook) => {
                let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
                unsafe {
                    ffi::sqlite3_update_hook(
                        self.db(),
                        Some(call_boxed_closure::<F>),
                        boxed_hook as *mut _,
                    )
                }
            }
            _ => unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) },
        };
        free_boxed_hook(previous_hook);
    }
}

fn free_boxed_hook(hook: *mut c_void) {
    if !hook.is_null() {
        // make sure that size_of::<*mut F>() is always equal to size_of::<*mut c_void>()
        let _: Box<*mut c_void> = unsafe { Box::from_raw(hook as *mut _) };
    }
}

#[cfg(test)]
mod test {
    use super::Action;
    use Connection;

    #[test]
    fn test_commit_hook() {
        let db = Connection::open_in_memory().unwrap();

        let mut called = false;
        db.commit_hook(Some(|| {
            called = true;
            false
        }));
        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
            .unwrap();
        assert!(called);
    }

    #[test]
    fn test_rollback_hook() {
        let db = Connection::open_in_memory().unwrap();

        let mut called = false;
        db.rollback_hook(Some(|| {
            called = true;
        }));
        db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")
            .unwrap();
        assert!(called);
    }

    #[test]
    fn test_update_hook() {
        let db = Connection::open_in_memory().unwrap();

        let mut called = false;
        db.update_hook(Some(|action, db: &str, tbl: &str, row_id| {
            assert_eq!(Action::SQLITE_INSERT, action);
            assert_eq!("main", db);
            assert_eq!("foo", tbl);
            assert_eq!(1, row_id);
            called = true;
        }));
        db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
        db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap();
        assert!(called);
    }
}
<|endoftext|>


<commit_before>/// According to glibc:
/// ```c
/// /* The x86-64 never uses Elf64_Rel/Elf32_Rel relocations.  */
/// #define ELF_MACHINE_NO_REL 1
/// #define ELF_MACHINE_NO_RELA 0
/// ```
use std::fmt;

use binary::elf::header::Header;
use binary::elf::program_header::{self, ProgramHeader};
use binary::elf::dyn::{self, Dyn};
use binary::elf::sym::{self, Sym};
use binary::elf::strtab::Strtab;
use binary::elf::rela::{self, Rela};
use binary::elf::gnu_hash::GnuHash;
use tls;

/// Computes the "load bias", which is normally the base.  However, in older Linux kernel's, 3.13, and whatever runs on travis, I have discovered that the kernel incorrectly maps the vdso with "bad" values.
///
/// Specifically, I'm seeing the `p_vaddr` value and `d_val` in the dynamic array showing up with overflow biased values, e.g.:
/// ```
/// p/x phdr.p_vaddr
/// $1 = 0xffffffffff700000
/// (gdb) p/x bias
/// $2 = 0x00007fff873f1000
/// ```
/// In newer kernels the `phdr.p_vaddr` (correctly) reports `0x468` (it's a `ET_DYN` after all), which is then safely added to the original bias/load address to recover the desired address in memory, in this case: 0x7ffff7ff7468.
/// Unfortunately, life is not so easy in 3.13, as we're told the `p_vaddr` (and the `d_val` in the dynamic entry entries) is a crazy value like above.  Luckily, after looking at what several dynamic linkers do, I noticed that they all seem to implement a version of the following, in that we can recover the correct address by relying on insane overflow arithmetic _regardless_ of whether we received this crazy address, or a normal address:
/// ```
/// load_bias = base - p_vaddr + p_offset
/// ```
/// In the 3.13 case:
/// ```
///    let load_bias = 0x7fff873f1000u64.wrapping_sub(0xffffffffff700000u64.wrapping_add(0));
///    println!("load_bias: 0x{:x}", load_bias);
///    let dynamic = load_bias.wrapping_add(0xffffffffff700468);
///    println!("dynamic: 0x{:x}", dynamic);
/// ```
/// On my machine with `4.4.5-1-ARCH`, the computed load bias will equal itself, and subsequent additions of sane `p_vaddr` values will work as expected.
/// As for why the above is the case on older kernels (or perhaps VMs only, I haven't tested extensively), I have no idea.
#[inline(always)]
pub unsafe fn compute_load_bias_wrapping(base: u64, phdrs:&[ProgramHeader]) -> usize {
    for phdr in phdrs {
        if phdr.p_type == program_header::PT_LOAD {
            return base.wrapping_sub(phdr.p_vaddr.wrapping_add(phdr.p_offset)) as usize;
        }
    }
    0
}

/// A `SharedObject` is either:
/// 1. an mmap'd dynamic library which is explicitly loaded by `dryad`
/// 2. the vdso provided by the kernel
/// 3. the executable we're interpreting
pub struct SharedObject<'process> {
    pub name: &'process str,
    pub load_bias: u64, // TODO: change to usize change this to addr or base_addr load_bias is stupid
    pub map_begin: u64, // probably remove these?
    pub map_end: u64,
    pub libs: Vec<&'process str>,
    pub phdrs: &'process[ProgramHeader],
    pub dynamic: &'process [Dyn],
    pub strtab: Strtab<'process>,
    pub symtab: &'process[Sym],
    pub relatab: &'process[Rela],
    pub pltrelatab: &'process[Rela],
    pub pltgot: *const u64,
    pub gnu_hash: Option<GnuHash<'process>>,
    pub load_path: Option<String>,
    pub flags: u64,
    pub state_flags: u64,
    pub tls: Option<tls::TlsInfo>,
}

impl<'process> fmt::Debug for SharedObject<'process> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "name: {} load_bias: {:x} DT_FLAGS: 0x{:x} DT_FLAGS_1 0x{:x}\n  ProgramHeaders: {:#?}\n  _DYNAMIC: {:#?}\n  String Table: {:#?}\n  Symbol Table: {:#?}\n  Rela Table: {:#?}\n  Plt Rela Table: {:#?}\n  Libraries: {:#?}\n",
               self.name, self.load_bias, self.flags, self.state_flags, self.phdrs, self.dynamic, self.strtab, self.symtab, self.relatab, self.pltrelatab, self.libs)
    }
}

impl<'process> SharedObject<'process> {

    /// Assumes the object referenced by the ptr has already been mmap'd or loaded into memory some way
    pub unsafe fn from_raw (ptr: u64) -> SharedObject<'process> {
        let header = &*(ptr as *const Header);
        let phdrs = ProgramHeader::from_raw_parts((header.e_phoff + ptr) as *const ProgramHeader, header.e_phnum as usize);
        let load_bias = compute_load_bias_wrapping(ptr, &phdrs);
        let dynamic = dyn::from_phdrs(load_bias as u64, phdrs).unwrap();
        let link_info = dyn::DynamicInfo::new(&dynamic, load_bias);
        let num_syms = (link_info.strtab - link_info.symtab) / sym::SIZEOF_SYM;
        let symtab = sym::from_raw(link_info.symtab as *const sym::Sym, num_syms);
        let strtab = Strtab::from_raw(link_info.strtab as *const u8, link_info.strsz as usize);
        let libs = dyn::get_needed(dynamic, &strtab, link_info.needed_count);
        let relatab = rela::from_raw(link_info.rela as *const rela::Rela, link_info.relasz);
        let pltrelatab = rela::from_raw(link_info.jmprel as *const rela::Rela, link_info.pltrelsz);
        let pltgot = if let Some(addr) = link_info.pltgot { addr } else { 0 };
        let gnu_hash = if let Some(addr) = link_info.gnu_hash { Some (GnuHash::new(addr as *const u32, symtab.len())) } else { None };
        SharedObject {
            name: strtab.get(link_info.soname),
            load_bias: ptr,
            map_begin: 0,
            map_end: 0,
            libs: libs,
            phdrs: phdrs,
            dynamic: dynamic,
            symtab: symtab,
            strtab: strtab,
            relatab: relatab,
            pltrelatab: pltrelatab,
            pltgot: pltgot as *const u64,
            gnu_hash: gnu_hash,
            load_path: None,
            flags: link_info.flags,
            state_flags: link_info.flags_1,
            tls: None,
        }

    }

    pub fn from_executable (name: &'static str, phdr_addr: u64, phnum: usize) -> Result<SharedObject<'process>, String> {
        unsafe {
            let addr = phdr_addr as *const ProgramHeader;
            let phdrs = ProgramHeader::from_raw_parts(addr, phnum);
//            let mut base = 0;
            let mut load_bias = 0;

            for phdr in phdrs {
                if phdr.p_type == program_header::PT_PHDR {
                    load_bias = phdr_addr - phdr.p_vaddr;
//                    base = phdr_addr - phdr.p_offset;
                    break;
                }
            }
            // if base == 0 then no PT_PHDR and we should terminate? or kernel should have noticed this and we needn't bother

            if let Some(dynamic) = dyn::from_phdrs(load_bias, phdrs) {

                let link_info = dyn::DynamicInfo::new(dynamic, load_bias as usize);
                // TODO: swap out the link_info syment with compile time constant SIZEOF_SYM?
                let num_syms = (link_info.strtab - link_info.symtab) / link_info.syment; // this _CAN'T_ generally be valid; but rdr has been doing it and scans every linux shared object binary without issue... so it must be right!
                let symtab = sym::from_raw(link_info.symtab as *const sym::Sym, num_syms);
                let strtab = Strtab::from_raw(link_info.strtab as *const u8, link_info.strsz);
                let libs = dyn::get_needed(dynamic, &strtab, link_info.needed_count);
                let relatab = rela::from_raw(link_info.rela as *const rela::Rela, link_info.relasz);
                let pltrelatab = rela::from_raw(link_info.jmprel as *const rela::Rela, link_info.pltrelsz);

                // TODO: fail with Err, not panic
                let pltgot = link_info.pltgot.expect("Error executable has no pltgot, aborting") as *const u64;
                let gnu_hash = if let Some(addr) = link_info.gnu_hash { Some (GnuHash::new(addr as *const u32, symtab.len())) } else { None };
                Ok (SharedObject {
                    name: name,
                    load_bias: load_bias,
                    map_begin: 0,
                    map_end: 0,
                    libs: libs,
                    phdrs: phdrs,
                    dynamic: dynamic,
                    symtab: symtab,
                    strtab: strtab,
                    relatab: relatab,
                    pltrelatab: pltrelatab,
                    pltgot: pltgot,
                    gnu_hash: gnu_hash,
                    load_path: Some (name.to_string()), // TODO: make absolute?,
                    flags: link_info.flags,
                    state_flags: link_info.flags_1,
                    tls: None,
                })

            } else {
                Err (format!("Error: executable {} has no _DYNAMIC array", name))
            }
        }
    }

    /// This is used by dryad's runtime symbol resolution
    pub fn find (&self, name: &str, hash: u32) -> Option<sym::Sym> {
//        println!("<{}.find> finding symbol: {}", self.name, symbol);
        match self.gnu_hash {
            Some (ref gnu_hash) => gnu_hash.find(hash, name, &self.strtab, &self.symtab),
            None => None
        }
    }

}

//unsafe impl<'a> Send for SharedObject<'a> {}
//unsafe impl<'a> Sync for SharedObject<'a> {}
<commit_msg>linker: upstream arg swap pos<commit_after>/// According to glibc:
/// ```c
/// /* The x86-64 never uses Elf64_Rel/Elf32_Rel relocations.  */
/// #define ELF_MACHINE_NO_REL 1
/// #define ELF_MACHINE_NO_RELA 0
/// ```
use std::fmt;

use binary::elf::header::Header;
use binary::elf::program_header::{self, ProgramHeader};
use binary::elf::dyn::{self, Dyn};
use binary::elf::sym::{self, Sym};
use binary::elf::strtab::Strtab;
use binary::elf::rela::{self, Rela};
use binary::elf::gnu_hash::GnuHash;
use tls;

/// Computes the "load bias", which is normally the base.  However, in older Linux kernel's, 3.13, and whatever runs on travis, I have discovered that the kernel incorrectly maps the vdso with "bad" values.
///
/// Specifically, I'm seeing the `p_vaddr` value and `d_val` in the dynamic array showing up with overflow biased values, e.g.:
/// ```
/// p/x phdr.p_vaddr
/// $1 = 0xffffffffff700000
/// (gdb) p/x bias
/// $2 = 0x00007fff873f1000
/// ```
/// In newer kernels the `phdr.p_vaddr` (correctly) reports `0x468` (it's a `ET_DYN` after all), which is then safely added to the original bias/load address to recover the desired address in memory, in this case: 0x7ffff7ff7468.
/// Unfortunately, life is not so easy in 3.13, as we're told the `p_vaddr` (and the `d_val` in the dynamic entry entries) is a crazy value like above.  Luckily, after looking at what several dynamic linkers do, I noticed that they all seem to implement a version of the following, in that we can recover the correct address by relying on insane overflow arithmetic _regardless_ of whether we received this crazy address, or a normal address:
/// ```
/// load_bias = base - p_vaddr + p_offset
/// ```
/// In the 3.13 case:
/// ```
///    let load_bias = 0x7fff873f1000u64.wrapping_sub(0xffffffffff700000u64.wrapping_add(0));
///    println!("load_bias: 0x{:x}", load_bias);
///    let dynamic = load_bias.wrapping_add(0xffffffffff700468);
///    println!("dynamic: 0x{:x}", dynamic);
/// ```
/// On my machine with `4.4.5-1-ARCH`, the computed load bias will equal itself, and subsequent additions of sane `p_vaddr` values will work as expected.
/// As for why the above is the case on older kernels (or perhaps VMs only, I haven't tested extensively), I have no idea.
#[inline(always)]
pub unsafe fn compute_load_bias_wrapping(base: u64, phdrs:&[ProgramHeader]) -> usize {
    for phdr in phdrs {
        if phdr.p_type == program_header::PT_LOAD {
            return base.wrapping_sub(phdr.p_vaddr.wrapping_add(phdr.p_offset)) as usize;
        }
    }
    0
}

/// A `SharedObject` is either:
/// 1. an mmap'd dynamic library which is explicitly loaded by `dryad`
/// 2. the vdso provided by the kernel
/// 3. the executable we're interpreting
pub struct SharedObject<'process> {
    pub name: &'process str,
    pub load_bias: u64, // TODO: change to usize change this to addr or base_addr load_bias is stupid
    pub map_begin: u64, // probably remove these?
    pub map_end: u64,
    pub libs: Vec<&'process str>,
    pub phdrs: &'process[ProgramHeader],
    pub dynamic: &'process [Dyn],
    pub strtab: Strtab<'process>,
    pub symtab: &'process[Sym],
    pub relatab: &'process[Rela],
    pub pltrelatab: &'process[Rela],
    pub pltgot: *const u64,
    pub gnu_hash: Option<GnuHash<'process>>,
    pub load_path: Option<String>,
    pub flags: u64,
    pub state_flags: u64,
    pub tls: Option<tls::TlsInfo>,
}

impl<'process> fmt::Debug for SharedObject<'process> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "name: {} load_bias: {:x} DT_FLAGS: 0x{:x} DT_FLAGS_1 0x{:x}\n  ProgramHeaders: {:#?}\n  _DYNAMIC: {:#?}\n  String Table: {:#?}\n  Symbol Table: {:#?}\n  Rela Table: {:#?}\n  Plt Rela Table: {:#?}\n  Libraries: {:#?}\n",
               self.name, self.load_bias, self.flags, self.state_flags, self.phdrs, self.dynamic, self.strtab, self.symtab, self.relatab, self.pltrelatab, self.libs)
    }
}

impl<'process> SharedObject<'process> {

    /// Assumes the object referenced by the ptr has already been mmap'd or loaded into memory some way
    pub unsafe fn from_raw (ptr: u64) -> SharedObject<'process> {
        let header = &*(ptr as *const Header);
        let phdrs = ProgramHeader::from_raw_parts((header.e_phoff + ptr) as *const ProgramHeader, header.e_phnum as usize);
        let load_bias = compute_load_bias_wrapping(ptr, &phdrs);
        let dynamic = dyn::from_phdrs(load_bias as u64, phdrs).unwrap();
        let link_info = dyn::DynamicInfo::new(&dynamic, load_bias);
        let num_syms = (link_info.strtab - link_info.symtab) / sym::SIZEOF_SYM;
        let symtab = sym::from_raw(link_info.symtab as *const sym::Sym, num_syms);
        let strtab = Strtab::from_raw(link_info.strtab as *const u8, link_info.strsz as usize);
        let libs = dyn::get_needed(dynamic, &strtab, link_info.needed_count);
        let relatab = rela::from_raw(link_info.rela as *const rela::Rela, link_info.relasz);
        let pltrelatab = rela::from_raw(link_info.jmprel as *const rela::Rela, link_info.pltrelsz);
        let pltgot = if let Some(addr) = link_info.pltgot { addr } else { 0 };
        let gnu_hash = if let Some(addr) = link_info.gnu_hash { Some (GnuHash::new(addr as *const u32, symtab.len())) } else { None };
        SharedObject {
            name: strtab.get(link_info.soname),
            load_bias: ptr,
            map_begin: 0,
            map_end: 0,
            libs: libs,
            phdrs: phdrs,
            dynamic: dynamic,
            symtab: symtab,
            strtab: strtab,
            relatab: relatab,
            pltrelatab: pltrelatab,
            pltgot: pltgot as *const u64,
            gnu_hash: gnu_hash,
            load_path: None,
            flags: link_info.flags,
            state_flags: link_info.flags_1,
            tls: None,
        }

    }

    pub fn from_executable (name: &'static str, phdr_addr: u64, phnum: usize) -> Result<SharedObject<'process>, String> {
        unsafe {
            let addr = phdr_addr as *const ProgramHeader;
            let phdrs = ProgramHeader::from_raw_parts(addr, phnum);
//            let mut base = 0;
            let mut load_bias = 0;

            for phdr in phdrs {
                if phdr.p_type == program_header::PT_PHDR {
                    load_bias = phdr_addr - phdr.p_vaddr;
//                    base = phdr_addr - phdr.p_offset;
                    break;
                }
            }
            // if base == 0 then no PT_PHDR and we should terminate? or kernel should have noticed this and we needn't bother

            if let Some(dynamic) = dyn::from_phdrs(load_bias, phdrs) {

                let link_info = dyn::DynamicInfo::new(dynamic, load_bias as usize);
                // TODO: swap out the link_info syment with compile time constant SIZEOF_SYM?
                let num_syms = (link_info.strtab - link_info.symtab) / link_info.syment; // this _CAN'T_ generally be valid; but rdr has been doing it and scans every linux shared object binary without issue... so it must be right!
                let symtab = sym::from_raw(link_info.symtab as *const sym::Sym, num_syms);
                let strtab = Strtab::from_raw(link_info.strtab as *const u8, link_info.strsz);
                let libs = dyn::get_needed(dynamic, &strtab, link_info.needed_count);
                let relatab = rela::from_raw(link_info.rela as *const rela::Rela, link_info.relasz);
                let pltrelatab = rela::from_raw(link_info.jmprel as *const rela::Rela, link_info.pltrelsz);

                // TODO: fail with Err, not panic
                let pltgot = link_info.pltgot.expect("Error executable has no pltgot, aborting") as *const u64;
                let gnu_hash = if let Some(addr) = link_info.gnu_hash { Some (GnuHash::new(addr as *const u32, symtab.len())) } else { None };
                Ok (SharedObject {
                    name: name,
                    load_bias: load_bias,
                    map_begin: 0,
                    map_end: 0,
                    libs: libs,
                    phdrs: phdrs,
                    dynamic: dynamic,
                    symtab: symtab,
                    strtab: strtab,
                    relatab: relatab,
                    pltrelatab: pltrelatab,
                    pltgot: pltgot,
                    gnu_hash: gnu_hash,
                    load_path: Some (name.to_string()), // TODO: make absolute?,
                    flags: link_info.flags,
                    state_flags: link_info.flags_1,
                    tls: None,
                })

            } else {
                Err (format!("Error: executable {} has no _DYNAMIC array", name))
            }
        }
    }

    /// This is used by dryad's runtime symbol resolution
    pub fn find (&self, name: &str, hash: u32) -> Option<sym::Sym> {
//        println!("<{}.find> finding symbol: {}", self.name, symbol);
        match self.gnu_hash {
            Some (ref gnu_hash) => gnu_hash.find(name, hash, &self.strtab, &self.symtab),
            None => None
        }
    }

}

//unsafe impl<'a> Send for SharedObject<'a> {}
//unsafe impl<'a> Sync for SharedObject<'a> {}
<|endoftext|>