<commit_before>//! Management of the directory layout of a build
//!
//! The directory layout is a little tricky at times, hence a separate file to
//! house this logic. The current layout looks like this:
//!
//! ```text
//! # This is the root directory for all output, the top-level package
//! # places all of its output here.
//! target/
//!
//!     # Cache of `rustc -Vv` output for performance.
//!     .rustc-info.json
//!
//!     # All final artifacts are linked into this directory from `deps`.
//!     # Note that named profiles will soon be included as separate directories
//!     # here. They have a restricted format, similar to Rust identifiers, so
//!     # Cargo-specific directories added in the future should use some prefix
//!     # like `.` to avoid name collisions.
//!     debug/  # or release/
//!
//!         # File used to lock the directory to prevent multiple cargo processes
//!         # from using it at the same time.
//!         .cargo-lock
//!
//!         # Hidden directory that holds all of the fingerprint files for all
//!         # packages
//!         .fingerprint/
//!             # Each package is in a separate directory.
//!             # Note that different target kinds have different filename prefixes.
//!             $pkgname-$META/
//!                 # Set of source filenames for this package.
//!                 dep-lib-$targetname
//!                 # Timestamp when this package was last built.
//!                 invoked.timestamp
//!                 # The fingerprint hash.
//!                 lib-$targetname
//!                 # Detailed information used for logging the reason why
//!                 # something is being recompiled.
//!                 lib-$targetname.json
//!                 # The console output from the compiler. This is cached
//!                 # so that warnings can be redisplayed for "fresh" units.
//!                 output-lib-$targetname
//!
//!         # This is the root directory for all rustc artifacts except build
//!         # scripts, examples, and test and bench executables. Almost every
//!         # artifact should have a metadata hash added to its filename to
//!         # prevent collisions. One notable exception is dynamic libraries.
//!         deps/
//!
//!         # Root directory for all compiled examples.
//!         examples/
//!
//!         # Directory used to store incremental data for the compiler (when
//!         # incremental is enabled.
//!         incremental/
//!
//!         # This is the location at which the output of all custom build
//!         # commands are rooted.
//!         build/
//!
//!             # Each package gets its own directory where its build script and
//!             # script output are placed
//!             $pkgname-$META/    # For the build script itself.
//!                 # The build script executable (name may be changed by user).
//!                 build-script-build-$META
//!                 # Hard link to build-script-build-$META.
//!                 build-script-build
//!                 # Dependency information generated by rustc.
//!                 build-script-build-$META.d
//!                 # Debug information, depending on platform and profile
//!                 # settings.
//!                 <debug symbols>
//!
//!             # The package shows up twice with two different metadata hashes.
//!             $pkgname-$META/  # For the output of the build script.
//!                 # Timestamp when the build script was last executed.
//!                 invoked.timestamp
//!                 # Directory where script can output files ($OUT_DIR).
//!                 out/
//!                 # Output from the build script.
//!                 output
//!                 # Path to `out`, used to help when the target directory is
//!                 # moved.
//!                 root-output
//!                 # Stderr output from the build script.
//!                 stderr
//!
//!     # Output from rustdoc
//!     doc/
//!
//!     # Used by `cargo package` and `cargo publish` to build a `.crate` file.
//!     package/
//!
//!     # Experimental feature for generated build scripts.
//!     .metabuild/
//! ```
//!
//! When cross-compiling, the layout is the same, except it appears in
//! `target/$TRIPLE`.

use crate::core::compiler::CompileTarget;
use crate::core::Workspace;
use crate::util::paths;
use crate::util::{CargoResult, FileLock};
use std::path::{Path, PathBuf};

/// Contains the paths of all target output locations.
///
/// See module docs for more information.
pub struct Layout {
    /// The root directory: `/path/to/target`.
    /// If cross compiling: `/path/to/target/$TRIPLE`.
    root: PathBuf,
    /// The final artifact destination: `$root/debug` (or `release`).
    dest: PathBuf,
    /// The directory with rustc artifacts: `$dest/deps`
    deps: PathBuf,
    /// The directory for build scripts: `$dest/build`
    build: PathBuf,
    /// The directory for incremental files: `$dest/incremental`
    incremental: PathBuf,
    /// The directory for fingerprints: `$dest/.fingerprint`
    fingerprint: PathBuf,
    /// The directory for examples: `$dest/examples`
    examples: PathBuf,
    /// The directory for rustdoc output: `$root/doc`
    doc: PathBuf,
    /// The lockfile for a build (`.cargo-lock`). Will be unlocked when this
    /// struct is `drop`ped.
    _lock: FileLock,
}

impl Layout {
    /// Calculate the paths for build output, lock the build directory, and return as a Layout.
    ///
    /// This function will block if the directory is already locked.
    ///
    /// `dest` should be the final artifact directory name. Currently either
    /// "debug" or "release".
    pub fn new(
        ws: &Workspace<'_>,
        target: Option<CompileTarget>,
        dest: &str,
    ) -> CargoResult<Layout> {
        let mut root = ws.target_dir();
        if let Some(target) = target {
            root.push(target.short_name());
        }
        let dest = root.join(dest);
        // If the root directory doesn't already exist go ahead and create it
        // here. Use this opportunity to exclude it from backups as well if the
        // system supports it since this is a freshly created folder.
        if !dest.as_path_unlocked().exists() {
            dest.create_dir()?;
            exclude_from_backups(dest.as_path_unlocked());
        }

        // For now we don't do any more finer-grained locking on the artifact
        // directory, so just lock the entire thing for the duration of this
        // compile.
        let lock = dest.open_rw(".cargo-lock", ws.config(), "build directory")?;
        let root = root.into_path_unlocked();
        let dest = dest.into_path_unlocked();

        Ok(Layout {
            deps: dest.join("deps"),
            build: dest.join("build"),
            incremental: dest.join("incremental"),
            fingerprint: dest.join(".fingerprint"),
            examples: dest.join("examples"),
            doc: root.join("doc"),
            root,
            dest,
            _lock: lock,
        })
    }

    /// Makes sure all directories stored in the Layout exist on the filesystem.
    pub fn prepare(&mut self) -> CargoResult<()> {
        paths::create_dir_all(&self.deps)?;
        paths::create_dir_all(&self.incremental)?;
        paths::create_dir_all(&self.fingerprint)?;
        paths::create_dir_all(&self.examples)?;
        paths::create_dir_all(&self.build)?;

        Ok(())
    }

    /// Fetch the destination path for final artifacts  (`/…/target/debug`).
    pub fn dest(&self) -> &Path {
        &self.dest
    }
    /// Fetch the deps path.
    pub fn deps(&self) -> &Path {
        &self.deps
    }
    /// Fetch the examples path.
    pub fn examples(&self) -> &Path {
        &self.examples
    }
    /// Fetch the doc path.
    pub fn doc(&self) -> &Path {
        &self.doc
    }
    /// Fetch the root path (`/…/target`).
    pub fn root(&self) -> &Path {
        &self.root
    }
    /// Fetch the incremental path.
    pub fn incremental(&self) -> &Path {
        &self.incremental
    }
    /// Fetch the fingerprint path.
    pub fn fingerprint(&self) -> &Path {
        &self.fingerprint
    }
    /// Fetch the build script path.
    pub fn build(&self) -> &Path {
        &self.build
    }
}

/// Marks the directory as excluded from archives/backups.
///
/// This is recommended to prevent derived/temporary files from bloating backups. There are two
/// mechanisms used to achieve this right now:
///
/// * A dedicated resource property excluding from Time Machine backups on macOS
/// * CACHEDIR.TAG files supported by various tools in a platform-independent way
fn exclude_from_backups(path: &Path) {
    exclude_from_time_machine(path);
    let _ = std::fs::write(
        path.join("CACHEDIR.TAG"),
        "Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/",
    );
    // Similarly to exclude_from_time_machine() we ignore errors here as it's an optional feature.
}

#[cfg(not(target_os = "macos"))]
fn exclude_from_time_machine(_: &Path) {}

#[cfg(target_os = "macos")]
/// Marks files or directories as excluded from Time Machine on macOS
fn exclude_from_time_machine(path: &Path) {
    use core_foundation::base::TCFType;
    use core_foundation::{number, string, url};
    use std::ptr;

    // For compatibility with 10.7 a string is used instead of global kCFURLIsExcludedFromBackupKey
    let is_excluded_key: Result<string::CFString, _> = "NSURLIsExcludedFromBackupKey".parse();
    let path = url::CFURL::from_path(path, false);
    if let (Some(path), Ok(is_excluded_key)) = (path, is_excluded_key) {
        unsafe {
            url::CFURLSetResourcePropertyForKey(
                path.as_concrete_TypeRef(),
                is_excluded_key.as_concrete_TypeRef(),
                number::kCFBooleanTrue as *const _,
                ptr::null_mut(),
            );
        }
    }
    // Errors are ignored, since it's an optional feature and failure
    // doesn't prevent Cargo from working
}
<commit_msg>Make target directory creation and backup exclusion atomic<commit_after>//! Management of the directory layout of a build
//!
//! The directory layout is a little tricky at times, hence a separate file to
//! house this logic. The current layout looks like this:
//!
//! ```text
//! # This is the root directory for all output, the top-level package
//! # places all of its output here.
//! target/
//!
//!     # Cache of `rustc -Vv` output for performance.
//!     .rustc-info.json
//!
//!     # All final artifacts are linked into this directory from `deps`.
//!     # Note that named profiles will soon be included as separate directories
//!     # here. They have a restricted format, similar to Rust identifiers, so
//!     # Cargo-specific directories added in the future should use some prefix
//!     # like `.` to avoid name collisions.
//!     debug/  # or release/
//!
//!         # File used to lock the directory to prevent multiple cargo processes
//!         # from using it at the same time.
//!         .cargo-lock
//!
//!         # Hidden directory that holds all of the fingerprint files for all
//!         # packages
//!         .fingerprint/
//!             # Each package is in a separate directory.
//!             # Note that different target kinds have different filename prefixes.
//!             $pkgname-$META/
//!                 # Set of source filenames for this package.
//!                 dep-lib-$targetname
//!                 # Timestamp when this package was last built.
//!                 invoked.timestamp
//!                 # The fingerprint hash.
//!                 lib-$targetname
//!                 # Detailed information used for logging the reason why
//!                 # something is being recompiled.
//!                 lib-$targetname.json
//!                 # The console output from the compiler. This is cached
//!                 # so that warnings can be redisplayed for "fresh" units.
//!                 output-lib-$targetname
//!
//!         # This is the root directory for all rustc artifacts except build
//!         # scripts, examples, and test and bench executables. Almost every
//!         # artifact should have a metadata hash added to its filename to
//!         # prevent collisions. One notable exception is dynamic libraries.
//!         deps/
//!
//!         # Root directory for all compiled examples.
//!         examples/
//!
//!         # Directory used to store incremental data for the compiler (when
//!         # incremental is enabled.
//!         incremental/
//!
//!         # This is the location at which the output of all custom build
//!         # commands are rooted.
//!         build/
//!
//!             # Each package gets its own directory where its build script and
//!             # script output are placed
//!             $pkgname-$META/    # For the build script itself.
//!                 # The build script executable (name may be changed by user).
//!                 build-script-build-$META
//!                 # Hard link to build-script-build-$META.
//!                 build-script-build
//!                 # Dependency information generated by rustc.
//!                 build-script-build-$META.d
//!                 # Debug information, depending on platform and profile
//!                 # settings.
//!                 <debug symbols>
//!
//!             # The package shows up twice with two different metadata hashes.
//!             $pkgname-$META/  # For the output of the build script.
//!                 # Timestamp when the build script was last executed.
//!                 invoked.timestamp
//!                 # Directory where script can output files ($OUT_DIR).
//!                 out/
//!                 # Output from the build script.
//!                 output
//!                 # Path to `out`, used to help when the target directory is
//!                 # moved.
//!                 root-output
//!                 # Stderr output from the build script.
//!                 stderr
//!
//!     # Output from rustdoc
//!     doc/
//!
//!     # Used by `cargo package` and `cargo publish` to build a `.crate` file.
//!     package/
//!
//!     # Experimental feature for generated build scripts.
//!     .metabuild/
//! ```
//!
//! When cross-compiling, the layout is the same, except it appears in
//! `target/$TRIPLE`.

use crate::core::compiler::CompileTarget;
use crate::core::Workspace;
use crate::util::paths;
use crate::util::{CargoResult, FileLock};
use std::fs;
use std::path::{Path, PathBuf};
use tempfile::Builder as TempFileBuilder;

/// Contains the paths of all target output locations.
///
/// See module docs for more information.
pub struct Layout {
    /// The root directory: `/path/to/target`.
    /// If cross compiling: `/path/to/target/$TRIPLE`.
    root: PathBuf,
    /// The final artifact destination: `$root/debug` (or `release`).
    dest: PathBuf,
    /// The directory with rustc artifacts: `$dest/deps`
    deps: PathBuf,
    /// The directory for build scripts: `$dest/build`
    build: PathBuf,
    /// The directory for incremental files: `$dest/incremental`
    incremental: PathBuf,
    /// The directory for fingerprints: `$dest/.fingerprint`
    fingerprint: PathBuf,
    /// The directory for examples: `$dest/examples`
    examples: PathBuf,
    /// The directory for rustdoc output: `$root/doc`
    doc: PathBuf,
    /// The lockfile for a build (`.cargo-lock`). Will be unlocked when this
    /// struct is `drop`ped.
    _lock: FileLock,
}

impl Layout {
    /// Calculate the paths for build output, lock the build directory, and return as a Layout.
    ///
    /// This function will block if the directory is already locked.
    ///
    /// `dest` should be the final artifact directory name. Currently either
    /// "debug" or "release".
    pub fn new(
        ws: &Workspace<'_>,
        target: Option<CompileTarget>,
        dest: &str,
    ) -> CargoResult<Layout> {
        let mut root = ws.target_dir();
        if let Some(target) = target {
            root.push(target.short_name());
        }
        // We need root to exist before we do the tempdir/rename dance inside it.
        if !root.as_path_unlocked().exists() {
            root.create_dir()?;
        }
        let dest = root.join(dest);
        // If the root directory doesn't already exist go ahead and create it
        // here. Use this opportunity to exclude it from backups as well if the
        // system supports it since this is a freshly created folder.
        //
        // We do this in two steps (first create a temporary directory and exlucde
        // it from backups, then rename it to the desired name. If we created the
        // directory directly where it should be and then excluded it from backups
        // we would risk a situation where cargo is interrupted right after the directory
        // creation but before the exclusion the the directory would remain non-excluded from
        // backups because we only perform exclusion right after we created the directory
        // ourselves.
        if !dest.as_path_unlocked().exists() {
            // We need the tempdir created in root instead of $TMP, because only then we can be
            // easily sure that rename() will succeed (the new name needs to be on the same mount
            // point as the old one).
            let tempdir = TempFileBuilder::new()
                .prefix("cargo-target")
                .tempdir_in(root.as_path_unlocked())?
                .into_path();
            exclude_from_backups(&tempdir);
            // Previously std::fs::create_dir_all() (through paths::create_dir_all()) was used
            // here to create the directory directly and fs::create_dir_all() explicitly treats
            // the directory being created concurrently by another thread or process as success,
            // hence the check below to follow the existing behavior. If we get an error at
            // rename() and suddently the directory (which didn't exist a moment earlier) exists
            // we can infer from it it's another cargo process doing work.
            if let Err(e) = fs::rename(&tempdir, dest.as_path_unlocked()) {
                if !dest.as_path_unlocked().exists() {
                    return Err(anyhow::Error::from(e));
                }
            }
        }

        // For now we don't do any more finer-grained locking on the artifact
        // directory, so just lock the entire thing for the duration of this
        // compile.
        let lock = dest.open_rw(".cargo-lock", ws.config(), "build directory")?;
        let root = root.into_path_unlocked();
        let dest = dest.into_path_unlocked();

        Ok(Layout {
            deps: dest.join("deps"),
            build: dest.join("build"),
            incremental: dest.join("incremental"),
            fingerprint: dest.join(".fingerprint"),
            examples: dest.join("examples"),
            doc: root.join("doc"),
            root,
            dest,
            _lock: lock,
        })
    }

    /// Makes sure all directories stored in the Layout exist on the filesystem.
    pub fn prepare(&mut self) -> CargoResult<()> {
        paths::create_dir_all(&self.deps)?;
        paths::create_dir_all(&self.incremental)?;
        paths::create_dir_all(&self.fingerprint)?;
        paths::create_dir_all(&self.examples)?;
        paths::create_dir_all(&self.build)?;

        Ok(())
    }

    /// Fetch the destination path for final artifacts  (`/…/target/debug`).
    pub fn dest(&self) -> &Path {
        &self.dest
    }
    /// Fetch the deps path.
    pub fn deps(&self) -> &Path {
        &self.deps
    }
    /// Fetch the examples path.
    pub fn examples(&self) -> &Path {
        &self.examples
    }
    /// Fetch the doc path.
    pub fn doc(&self) -> &Path {
        &self.doc
    }
    /// Fetch the root path (`/…/target`).
    pub fn root(&self) -> &Path {
        &self.root
    }
    /// Fetch the incremental path.
    pub fn incremental(&self) -> &Path {
        &self.incremental
    }
    /// Fetch the fingerprint path.
    pub fn fingerprint(&self) -> &Path {
        &self.fingerprint
    }
    /// Fetch the build script path.
    pub fn build(&self) -> &Path {
        &self.build
    }
}

/// Marks the directory as excluded from archives/backups.
///
/// This is recommended to prevent derived/temporary files from bloating backups. There are two
/// mechanisms used to achieve this right now:
///
/// * A dedicated resource property excluding from Time Machine backups on macOS
/// * CACHEDIR.TAG files supported by various tools in a platform-independent way
fn exclude_from_backups(path: &Path) {
    exclude_from_time_machine(path);
    let _ = std::fs::write(
        path.join("CACHEDIR.TAG"),
        "Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/",
    );
    // Similarly to exclude_from_time_machine() we ignore errors here as it's an optional feature.
}

#[cfg(not(target_os = "macos"))]
fn exclude_from_time_machine(_: &Path) {}

#[cfg(target_os = "macos")]
/// Marks files or directories as excluded from Time Machine on macOS
fn exclude_from_time_machine(path: &Path) {
    use core_foundation::base::TCFType;
    use core_foundation::{number, string, url};
    use std::ptr;

    // For compatibility with 10.7 a string is used instead of global kCFURLIsExcludedFromBackupKey
    let is_excluded_key: Result<string::CFString, _> = "NSURLIsExcludedFromBackupKey".parse();
    let path = url::CFURL::from_path(path, false);
    if let (Some(path), Ok(is_excluded_key)) = (path, is_excluded_key) {
        unsafe {
            url::CFURLSetResourcePropertyForKey(
                path.as_concrete_TypeRef(),
                is_excluded_key.as_concrete_TypeRef(),
                number::kCFBooleanTrue as *const _,
                ptr::null_mut(),
            );
        }
    }
    // Errors are ignored, since it's an optional feature and failure
    // doesn't prevent Cargo from working
}
<|endoftext|>


<commit_before>#[cfg(feature="trace_log")] extern crate env_logger;

extern crate radeco_lib;
extern crate r2pipe;
extern crate r2api;
extern crate petgraph;

mod cli;

use std::env;
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process;
use petgraph::graph::NodeIndex;

use r2pipe::r2::R2;
use r2api::api_trait::R2Api;
use radeco_lib::analysis::cse::cse::CSE;
use radeco_lib::analysis::sccp;
//use radeco_lib::analysis::valueset::analyzer_wysinwyx::FnAnalyzer;
//use radeco_lib::analysis::valueset::mem_structs::{A_Loc,AbstractAddress};
use radeco_lib::analysis::interproc::fixcall::CallFixer;
use radeco_lib::frontend::radeco_containers::{ProjectLoader, RadecoProject, RadecoModule};
use radeco_lib::middle::{dce, dot};
use radeco_lib::middle::ir_writer::IRWriter;
use radeco_lib::middle::ir_reader::parse_il;
use radeco_lib::middle::ssa::memoryssa::MemorySSA;
use radeco_lib::middle::ssa::verifier;

//use radeco_lib::analysis::mark_refs;

const USAGE: &'static str = "
Usage: minidec [-f <names>...] <target>

Options:
    -f, --functions  Analayze only some functions
";

fn main() {
    #[cfg(feature="trace_log")] env_logger::init();

    let requested_functions = cli::init_for_args(USAGE);

    let proj_name = env::args().nth(env::args().len() -1).unwrap();
    let mut rproj = {
        ProjectLoader::new().path(&proj_name).load()
    };
    let regfile = rproj.regfile().clone();
    for mut xy in rproj.iter_mut() {
        let rmod = &mut xy.module;
        let mut dir = PathBuf::from(".");
        dir.push(format!("{}_out", proj_name));
        dir.push(format!("{}_out", rmod.name()));
        fs::create_dir_all(&dir).expect("Failed to create directory");

        // Reduce the complexity of rmod.functions to just a vec of (u64,&String)
        // for easier extraction and matching

        // Analyze preserved for all functions.
        {
            println!("[*] Fixing Callee Information");
            let bp_name = regfile.get_name_by_alias(&"BP".to_string());
            let sp_name = regfile.get_name_by_alias(&"SP".to_string());
            let mut callfixer = CallFixer::new(rmod, bp_name, sp_name);
            callfixer.rounded_analysis();
        }
        // Filter the data if the user provided some args to be matched upon
        let matched_func_addrs = if requested_functions.len() != 0 {

            //XXX Impl in more efficient way
            let mut matched_func_vec: Vec<(u64, &str)> =
                rmod.iter().map(|_f| {
                    let f = _f.function.1;
                    (f.offset.clone(), &*f.name)
                }).collect();
                // rmod.iter().map(|(fn_addr, rfn)| (fn_addr.clone(), &rfn.name)).collect();

            let all_func_names: Vec<(&str)> =
                matched_func_vec.iter().map(|&(_, name)| name).collect();
            matched_func_vec = filter_with(&matched_func_vec,
                                           &requested_functions.iter().map(|s| &s[..]).collect::<Vec<_>>());
            cli::print_match_summary(&matched_func_vec,
                                     &requested_functions.iter().map(|s| &s[..]).collect::<Vec<_>>(),
                                     &all_func_names);
            matched_func_vec.into_iter().map(|(addr, _)| addr).collect::<Vec<_>>()
        } else {
            Vec::new()
        };

        // Main file to contain IRs of all rfns
        let mut ffm = {
            let mut fname = PathBuf::from(&dir);
            fname.push("main");
            File::create(&fname).expect("Unable to create file")
        };

        for addr in matched_func_addrs {

            let ref mut rfn = rmod.functions.get_mut(&addr).unwrap();

            println!("[+] Analyzing: {} @ {:#x}", rfn.name, addr);
            {
                println!("  [*] Eliminating Dead Code");
                dce::collect(rfn.ssa_mut());
            }
            let mut ssa = {
                // Constant Propagation (sccp)
                println!("  [*] Propagating Constants");
                let mut analyzer = sccp::Analyzer::new(rfn.ssa_mut());
                analyzer.analyze();
                analyzer.emit_ssa()
            };
            {
                println!("  [*] Eliminating More DeadCode");
                dce::collect(&mut ssa);
            }
            {
                // Common SubExpression Elimination (cse)
                println!("  [*] Eliminating Common SubExpressions");
                let mut cse = CSE::new(rfn.ssa_mut());
                cse.run();
            }
            {
                // Verify SSA
                println!("  [*] Verifying SSA's Validity");
                match verifier::verify(rfn.ssa()) {
                    Err(e) => {
                        println!("  [*] Found Error: {}", e);
                        process::exit(255);
                    }
                    Ok(_) => {  }
                }
            }
            {
                // Building memory SSA.
                let _memory_ssa = {
                    // Generate MemorySSA
                    println!("  [*] Generating Memory SSA");
                    let mut mssa = MemorySSA::new(rfn.ssa());
                    //TODO issue119
                    mssa.gather_variables(rfn.datarefs(), rfn.locals(),
                                          &rfn.call_sites(&rmod.callgraph));
                    mssa.run();
                    mssa
                };
            }
            //if false {
            //    if (!rfn.name.eq("sym.main")) & (!rfn.name.eq("main")) {
            //        continue;
            //    }
            //    println!("  [*] Analyzing Value Sets");
            //    let fn_analyzer = FnAnalyzer::from((*rfn).clone());
            //    let a_store_fn = fn_analyzer.analyze_rfn();
            //    for (a_loc, strided_interval) in a_store_fn.store {
            //        if let A_Loc{addr: AbstractAddress::Node{..}, ..} = a_loc {
            //            continue;
            //        };
            //        println!("{}", a_loc);
            //        println!("Strided Interval: {}", strided_interval);
            //    };
            //}

            //{
                //// Expreimental reference marking pass
                //println!("  [*] Unstable> Marking References");
                //let mut rmark = mark_refs::ReferenceMarker { };
                //rmark.resolve_refs(&mut rfn.ssa);
            //}

            let mut fname = PathBuf::from(&dir);
            fname.push(rfn.name.as_ref());

            {
                ///////////////////////////
                // Write out the IR file
                //////////////////////////
                println!("  [*] Writing out IR");
                let mut ff = File::create(&fname).expect("Unable to create file");
                let mut writer: IRWriter = Default::default();
                let res = writer.emit_il(Some(rfn.name.to_string()), rfn.ssa());
                writeln!(ff, "{}", res).expect("Error writing to file");
                writeln!(ffm, "{}", res).expect("Error writing to file");
                // Set as a comment in radare2
                rmod.source.as_mut().unwrap().send(&format!("CC, {} @ {}", fname.to_str().unwrap(), addr));
            }

            {
                //////////////////////////////
                // Try parsing the IR file
                /////////////////////////////
                println!("  [*] Testing IR parser");
                let mut ir_file = File::open(&fname).expect("Unable to open file");
                let mut file_contents = String::new();
                ir_file.read_to_string(&mut file_contents).expect("Error reading file");
                let parsed = parse_il(&file_contents);
                let mut writer: IRWriter = Default::default();
                let res = writer.emit_il(Some((*rfn.name).to_owned()), &parsed);
                for (orig, roundtrip) in file_contents.lines().zip(res.lines()) {
                    if orig != roundtrip {
                        println!("  FAILED TO ROUND-TRIP: \"{}\" => \"{}\"", orig, roundtrip);
                    }
                }
            }

            {
                ////////////////////////
                // Generate DOT file
                ///////////////////////
                println!("  [*] Generating dot");
                fname.set_extension("dot");
                let mut df = File::create(&fname).expect("Unable to create .dot file");
                let dot = dot::emit_dot(rfn.ssa());
                writeln!(df, "{}", dot).expect("Error writing to file");
            }
        }

        rmod.source.as_mut().unwrap().send("e scr.color=true");
    }
}

// Filters the functions that were in Radeco output AND requested by user
fn filter_with<'a>(all_funcs: &Vec<(u64, &'a str)>,
                   requested: &Vec<&'a str>)
                   -> Vec<(u64, &'a str)> {

    all_funcs.iter()
        .filter(|&&(_, name)| requested.iter().any(|user_req| &name == user_req))
        .map(|&(addr, name)| (addr, name))
        .collect()
}
<commit_msg>Parse IR that was just emitted instead of rereading it from file<commit_after>#[cfg(feature="trace_log")] extern crate env_logger;

extern crate radeco_lib;
extern crate r2pipe;
extern crate r2api;
extern crate petgraph;

mod cli;

use std::env;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process;
use petgraph::graph::NodeIndex;

use r2pipe::r2::R2;
use r2api::api_trait::R2Api;
use radeco_lib::analysis::cse::cse::CSE;
use radeco_lib::analysis::sccp;
//use radeco_lib::analysis::valueset::analyzer_wysinwyx::FnAnalyzer;
//use radeco_lib::analysis::valueset::mem_structs::{A_Loc,AbstractAddress};
use radeco_lib::analysis::interproc::fixcall::CallFixer;
use radeco_lib::frontend::radeco_containers::{ProjectLoader, RadecoProject, RadecoModule};
use radeco_lib::middle::{dce, dot};
use radeco_lib::middle::ir_writer::IRWriter;
use radeco_lib::middle::ir_reader::parse_il;
use radeco_lib::middle::ssa::memoryssa::MemorySSA;
use radeco_lib::middle::ssa::verifier;

//use radeco_lib::analysis::mark_refs;

const USAGE: &'static str = "
Usage: minidec [-f <names>...] <target>

Options:
    -f, --functions  Analayze only some functions
";

fn main() {
    #[cfg(feature="trace_log")] env_logger::init();

    let requested_functions = cli::init_for_args(USAGE);

    let proj_name = env::args().nth(env::args().len() -1).unwrap();
    let mut rproj = {
        ProjectLoader::new().path(&proj_name).load()
    };
    let regfile = rproj.regfile().clone();
    for mut xy in rproj.iter_mut() {
        let rmod = &mut xy.module;
        let mut dir = PathBuf::from(".");
        dir.push(format!("{}_out", proj_name));
        dir.push(format!("{}_out", rmod.name()));
        fs::create_dir_all(&dir).expect("Failed to create directory");

        // Reduce the complexity of rmod.functions to just a vec of (u64,&String)
        // for easier extraction and matching

        // Analyze preserved for all functions.
        {
            println!("[*] Fixing Callee Information");
            let bp_name = regfile.get_name_by_alias(&"BP".to_string());
            let sp_name = regfile.get_name_by_alias(&"SP".to_string());
            let mut callfixer = CallFixer::new(rmod, bp_name, sp_name);
            callfixer.rounded_analysis();
        }
        // Filter the data if the user provided some args to be matched upon
        let matched_func_addrs = if requested_functions.len() != 0 {

            //XXX Impl in more efficient way
            let mut matched_func_vec: Vec<(u64, &str)> =
                rmod.iter().map(|_f| {
                    let f = _f.function.1;
                    (f.offset.clone(), &*f.name)
                }).collect();
                // rmod.iter().map(|(fn_addr, rfn)| (fn_addr.clone(), &rfn.name)).collect();

            let all_func_names: Vec<(&str)> =
                matched_func_vec.iter().map(|&(_, name)| name).collect();
            matched_func_vec = filter_with(&matched_func_vec,
                                           &requested_functions.iter().map(|s| &s[..]).collect::<Vec<_>>());
            cli::print_match_summary(&matched_func_vec,
                                     &requested_functions.iter().map(|s| &s[..]).collect::<Vec<_>>(),
                                     &all_func_names);
            matched_func_vec.into_iter().map(|(addr, _)| addr).collect::<Vec<_>>()
        } else {
            Vec::new()
        };

        // Main file to contain IRs of all rfns
        let mut ffm = {
            let mut fname = PathBuf::from(&dir);
            fname.push("main");
            File::create(&fname).expect("Unable to create file")
        };

        for addr in matched_func_addrs {

            let ref mut rfn = rmod.functions.get_mut(&addr).unwrap();

            println!("[+] Analyzing: {} @ {:#x}", rfn.name, addr);
            {
                println!("  [*] Eliminating Dead Code");
                dce::collect(rfn.ssa_mut());
            }
            let mut ssa = {
                // Constant Propagation (sccp)
                println!("  [*] Propagating Constants");
                let mut analyzer = sccp::Analyzer::new(rfn.ssa_mut());
                analyzer.analyze();
                analyzer.emit_ssa()
            };
            {
                println!("  [*] Eliminating More DeadCode");
                dce::collect(&mut ssa);
            }
            {
                // Common SubExpression Elimination (cse)
                println!("  [*] Eliminating Common SubExpressions");
                let mut cse = CSE::new(rfn.ssa_mut());
                cse.run();
            }
            {
                // Verify SSA
                println!("  [*] Verifying SSA's Validity");
                match verifier::verify(rfn.ssa()) {
                    Err(e) => {
                        println!("  [*] Found Error: {}", e);
                        process::exit(255);
                    }
                    Ok(_) => {  }
                }
            }
            {
                // Building memory SSA.
                let _memory_ssa = {
                    // Generate MemorySSA
                    println!("  [*] Generating Memory SSA");
                    let mut mssa = MemorySSA::new(rfn.ssa());
                    //TODO issue119
                    mssa.gather_variables(rfn.datarefs(), rfn.locals(),
                                          &rfn.call_sites(&rmod.callgraph));
                    mssa.run();
                    mssa
                };
            }
            //if false {
            //    if (!rfn.name.eq("sym.main")) & (!rfn.name.eq("main")) {
            //        continue;
            //    }
            //    println!("  [*] Analyzing Value Sets");
            //    let fn_analyzer = FnAnalyzer::from((*rfn).clone());
            //    let a_store_fn = fn_analyzer.analyze_rfn();
            //    for (a_loc, strided_interval) in a_store_fn.store {
            //        if let A_Loc{addr: AbstractAddress::Node{..}, ..} = a_loc {
            //            continue;
            //        };
            //        println!("{}", a_loc);
            //        println!("Strided Interval: {}", strided_interval);
            //    };
            //}

            //{
                //// Expreimental reference marking pass
                //println!("  [*] Unstable> Marking References");
                //let mut rmark = mark_refs::ReferenceMarker { };
                //rmark.resolve_refs(&mut rfn.ssa);
            //}

            let mut fname = PathBuf::from(&dir);
            fname.push(rfn.name.as_ref());

            let fn_ir_str = {
                ///////////////////////////
                // Write out the IR file
                //////////////////////////
                println!("  [*] Writing out IR");
                let mut ff = File::create(&fname).expect("Unable to create file");
                let mut writer: IRWriter = Default::default();
                let res = writer.emit_il(Some(rfn.name.to_string()), rfn.ssa());
                writeln!(ff, "{}", res).expect("Error writing to file");
                writeln!(ffm, "{}", res).expect("Error writing to file");
                // Set as a comment in radare2
                rmod.source.as_mut().unwrap().send(&format!("CC, {} @ {}", fname.to_str().unwrap(), addr));
                res
            };

            {
                //////////////////////////////
                // Try parsing the IR file
                /////////////////////////////
                println!("  [*] Testing IR parser");
                let parsed = parse_il(&fn_ir_str);
                let mut writer: IRWriter = Default::default();
                let res = writer.emit_il(Some((*rfn.name).to_owned()), &parsed);
                for (orig, roundtrip) in fn_ir_str.lines().zip(res.lines()) {
                    if orig != roundtrip {
                        println!("  FAILED TO ROUND-TRIP: \"{}\" => \"{}\"", orig, roundtrip);
                    }
                }
            }

            {
                ////////////////////////
                // Generate DOT file
                ///////////////////////
                println!("  [*] Generating dot");
                fname.set_extension("dot");
                let mut df = File::create(&fname).expect("Unable to create .dot file");
                let dot = dot::emit_dot(rfn.ssa());
                writeln!(df, "{}", dot).expect("Error writing to file");
            }
        }

        rmod.source.as_mut().unwrap().send("e scr.color=true");
    }
}

// Filters the functions that were in Radeco output AND requested by user
fn filter_with<'a>(all_funcs: &Vec<(u64, &'a str)>,
                   requested: &Vec<&'a str>)
                   -> Vec<(u64, &'a str)> {

    all_funcs.iter()
        .filter(|&&(_, name)| requested.iter().any(|user_req| &name == user_req))
        .map(|&(addr, name)| (addr, name))
        .collect()
}
<|endoftext|>


<commit_before>extern crate cairo;

extern crate gtk;
use gtk::{Inhibit, ToolButtonExt, WidgetExt};

#[cfg(feature = "profiling-audio-draw")]
use chrono::Utc;

use std::boxed::Box;

use std::rc::Rc;
use std::cell::RefCell;

use std::sync::{Arc, Mutex};

use media::{Context, DoubleAudioBuffer, SampleExtractor, Timestamp};

use super::{BACKGROUND_COLOR, ControllerState, DoubleWaveformBuffer, MainController,
            WaveformConditions, WaveformBuffer};

const BUFFER_DURATION:   u64 = 60_000_000_000;    // 60 s
const MIN_REQ_DURATION:  f64 =      1_953_125f64; //  2 ms / 1000 px
const MAX_REQ_DURATION:  f64 = 32_000_000_000f64; // 32 s  / 1000 px
const INIT_REQ_DURATION: f64 =  4_000_000_000f64; //  4 s  / 1000 px
const STEP_REQ_DURATION: f64 =  2f64;

pub struct AudioController {
    container: gtk::Container,
    drawingarea: gtk::DrawingArea,
    zoom_in_btn: gtk::ToolButton,
    zoom_out_btn: gtk::ToolButton,

    is_active: bool,

    requested_duration: Rc<RefCell<f64>>,

    waveform_mtx: Arc<Mutex<Box<SampleExtractor>>>,
    dbl_buffer_mtx: Arc<Mutex<DoubleAudioBuffer>>,
}

impl AudioController {
    pub fn new(builder: &gtk::Builder) -> Self {
        let dbl_buffer_mtx = DoubleWaveformBuffer::new(BUFFER_DURATION);
        let waveform_mtx = dbl_buffer_mtx.lock()
            .expect("AudioController::new: couldn't lock dbl_buffer_mtx")
            .get_exposed_buffer_mtx();

        AudioController {
            container: builder.get_object("audio-container").unwrap(),
            drawingarea: builder.get_object("audio-drawingarea").unwrap(),
            zoom_in_btn: builder.get_object("audio_zoom_in-toolbutton").unwrap(),
            zoom_out_btn: builder.get_object("audio_zoom_out-toolbutton").unwrap(),

            is_active: false,
            requested_duration: Rc::new(RefCell::new(INIT_REQ_DURATION)),
            waveform_mtx: waveform_mtx,
            dbl_buffer_mtx: dbl_buffer_mtx,
        }
    }

    pub fn register_callbacks(&self, main_ctrl: &Rc<RefCell<MainController>>) {
        // draw
        let waveform_mtx = Arc::clone(&self.waveform_mtx);
        let requested_duration = Rc::clone(&self.requested_duration);
        self.drawingarea.connect_draw(move |drawing_area, cairo_ctx| {
            AudioController::draw(
                drawing_area,
                cairo_ctx,
                &waveform_mtx,
                *requested_duration.borrow(),
            ).into()
        });

        // widget size changed
        let requested_duration = Rc::clone(&self.requested_duration);
        let dbl_buffer_mtx = Arc::clone(&self.dbl_buffer_mtx);
        self.drawingarea.connect_size_allocate(move |drawingarea, _| {
            AudioController::refresh(
                drawingarea,
                *requested_duration.borrow(),
                &dbl_buffer_mtx,
            );
        });

        // click in drawing_area
        let main_ctrl_rc = Rc::clone(&main_ctrl);
        let waveform_mtx = Arc::clone(&self.waveform_mtx);
        self.drawingarea.connect_button_press_event(move |_, event_button| {
            let button = event_button.get_button();
            if button == 1 {
                if let Some(position) = {
                    let waveform_buffer_grd = &mut *waveform_mtx.lock()
                        .expect("Couldn't lock waveform buffer in audio controller draw");
                    waveform_buffer_grd
                        .as_mut_any().downcast_mut::<WaveformBuffer>()
                        .expect("SamplesExtratctor is not a waveform buffer in audio controller draw")
                        .get_position(event_button.get_position().0)
                }
                {
                    main_ctrl_rc.borrow_mut().seek(position, true); // accurate (slow)
                }
            }
            Inhibit(true)
        });

        // click zoom in
        let drawingarea = self.drawingarea.clone();
        let requested_duration = Rc::clone(&self.requested_duration);
        let dbl_buffer_mtx = Arc::clone(&self.dbl_buffer_mtx);
        self.zoom_in_btn.connect_clicked(move |_| {
            let (can_update, duration) = {
                let mut duration_grd = requested_duration.borrow_mut();
                *duration_grd /= STEP_REQ_DURATION;
                if *duration_grd >= MIN_REQ_DURATION {
                    (true, *duration_grd)
                } else {
                    *duration_grd = MIN_REQ_DURATION;
                    (false, *duration_grd)
                }
            };

            if can_update {
                AudioController::refresh(
                    &drawingarea,
                    duration,
                    &dbl_buffer_mtx,
                );
            }
        });

        // click zoom out
        let drawingarea = self.drawingarea.clone();
        let requested_duration = Rc::clone(&self.requested_duration);
        let dbl_buffer_mtx = Arc::clone(&self.dbl_buffer_mtx);
        self.zoom_out_btn.connect_clicked(move |_| {
            let (can_update, duration) = {
                let mut duration_grd = requested_duration.borrow_mut();
                *duration_grd *= STEP_REQ_DURATION;
                if *duration_grd <= MAX_REQ_DURATION {
                    (true, *duration_grd)
                } else {
                    *duration_grd = MAX_REQ_DURATION;
                    (false, *duration_grd)
                }
            };

            if can_update {
                AudioController::refresh(
                    &drawingarea,
                    duration,
                    &dbl_buffer_mtx,
                );
            }
        });
    }

    pub fn cleanup(&mut self) {
        self.is_active = false;
        {
            self.dbl_buffer_mtx.lock()
                .expect("AudioController::cleanup: Couldn't lock dbl_buffer_mtx")
                .cleanup();
        }
        *self.requested_duration.borrow_mut() = INIT_REQ_DURATION;
        self.drawingarea.queue_draw();
    }

    pub fn get_dbl_buffer_mtx(&self) -> Arc<Mutex<DoubleAudioBuffer>> {
        Arc::clone(&self.dbl_buffer_mtx)
    }

    pub fn new_media(&mut self, context: &Context) {
        let has_audio = context.info.lock()
                .expect("Failed to lock media info while initializing audio controller")
                .audio_best
                .is_some();

        if has_audio {
            self.is_active = true;
            self.container.show();
        } else {
            self.container.hide();
        }
    }

    pub fn seek(&mut self, position: u64, state: &ControllerState) {
        {
            self.waveform_mtx.lock()
                .expect("AudioController::seek: Couldn't lock waveform_mtx")
                .as_mut_any().downcast_mut::<WaveformBuffer>()
                .expect("AudioController::seek: SamplesExtratctor is not a WaveformBuffer")
                .seek(position, *state == ControllerState::Playing);
        }

        if *state == ControllerState::Paused {
            // refresh the buffer in order to render the waveform
            // with samples that might not be rendered in current WaveformImage yet
            self.dbl_buffer_mtx.lock()
                .expect("AudioController::seek: couldn't lock dbl_buffer_mtx")
                .refresh();
        }
        self.tick();
    }

    pub fn tick(&self) {
        if self.is_active {
            self.drawingarea.queue_draw();
        }
    }

    fn clean_cairo_context(cr: &cairo::Context) {
        cr.set_source_rgb(
            BACKGROUND_COLOR.0,
            BACKGROUND_COLOR.1,
            BACKGROUND_COLOR.2
        );
        cr.paint();
    }

    fn draw(
        drawingarea: &gtk::DrawingArea,
        cr: &cairo::Context,
        waveform_mtx: &Arc<Mutex<Box<SampleExtractor>>>,
        requested_duration: f64,
    ) -> Inhibit {
        #[cfg(feature = "profiling-audio-draw")]
        let before_init = Utc::now();

        let allocation = drawingarea.get_allocation();
        if allocation.width.is_negative() {
            println!("negative allocation.width: {}", allocation.width);
            return Inhibit(true);
        }

        #[cfg(feature = "profiling-audio-draw")]
        let before_lock = Utc::now();
        #[cfg(feature = "profiling-audio-draw")]
        let mut _before_cndt = Utc::now();
        #[cfg(feature = "profiling-audio-draw")]
        let mut _before_image = Utc::now();

        let (first_pos, last_opt, cursor_opt) = {
            let waveform_grd = &mut *waveform_mtx.lock()
                .expect("AudioController::draw: couldn't lock waveform_mtx");
            let waveform_buffer = waveform_grd
                .as_mut_any().downcast_mut::<WaveformBuffer>()
                .expect("AudioController::draw: SamplesExtratctor is not a WaveformBuffer");

            #[cfg(feature = "profiling-audio-draw")]
            let _before_cndt = Utc::now();

            waveform_buffer.update_conditions(
                requested_duration,
                allocation.width,
                allocation.height
            );

            match waveform_buffer.get_image() {
                Some((image, x_offset, first_pos, last_opt, cursor_opt)) => {
                    #[cfg(feature = "profiling-audio-draw")]
                    let _before_image = Utc::now();

                    cr.set_source_surface(image, -x_offset, 0f64);
                    cr.paint();

                    (first_pos, last_opt, cursor_opt)
                },
                None => {
                    AudioController::clean_cairo_context(cr);
                    return Inhibit(true)
                },
            }
        };

        #[cfg(feature = "profiling-audio-draw")]
        let before_pos = Utc::now();

        let height = f64::from(allocation.height);
        let width = f64::from(allocation.width);
        cr.scale(1f64, 1f64);
        cr.set_source_rgb(1f64, 1f64, 0f64);
        cr.set_font_size(14f64);

        // first position
        let first_text = format!("{}", Timestamp::format(first_pos, false));
        let first_text_width = 2f64 + cr.text_extents(&first_text).width;
        cr.move_to(2f64, 30f64);
        cr.show_text(&first_text);

        // last position
        if let Some((last_x, last_pos)) = last_opt {
            let last_text = format!("{}", Timestamp::format(last_pos, false));
            let last_text_width = 2f64 + cr.text_extents(&last_text).width;
            if last_x + last_text_width > first_text_width + 10f64 {
                // last test won't overlap with first text
                cr.move_to(last_x - last_text_width, 30f64);
                cr.show_text(&last_text);
            }
        }

        if let Some((current_x, current_pos)) = cursor_opt {
            // draw current pos
            let cursor_text = format!("{}", Timestamp::format(current_pos, true));
            let cursor_text_width = 10f64 + cr.text_extents(&cursor_text).width;
            let cursor_text_x =
                if current_x + cursor_text_width < width {
                    current_x + 10f64
                } else {
                    current_x - cursor_text_width
                };
            cr.move_to(cursor_text_x, 15f64);
            cr.show_text(&cursor_text);

            cr.set_line_width(1f64);
            cr.move_to(current_x, 0f64);
            cr.line_to(current_x, height);
            cr.stroke();
        }

        #[cfg(feature = "profiling-audio-draw")]
        let end = Utc::now();

        #[cfg(feature = "profiling-audio-draw")]
        println!("audio-draw,{},{},{},{},{},{}",
            before_init.time().format("%H:%M:%S%.6f"),
            before_lock.time().format("%H:%M:%S%.6f"),
            _before_cndt.time().format("%H:%M:%S%.6f"),
            _before_image.time().format("%H:%M:%S%.6f"),
            before_pos.time().format("%H:%M:%S%.6f"),
            end.time().format("%H:%M:%S%.6f"),
        );

       Inhibit(true)
    }

    fn refresh(
        drawingarea: &gtk::DrawingArea,
        requested_duration: f64,
        dbl_buffer_mtx: &Arc<Mutex<DoubleAudioBuffer>>,
    ) {
        let allocation = drawingarea.get_allocation();
        {
            // refresh the buffer in order to render the waveform
            // in latest conditions
            dbl_buffer_mtx.lock()
                .expect("AudioController::size-allocate: couldn't lock dbl_buffer_mtx")
                .refresh_with_conditions(
                    Box::new(WaveformConditions::new(
                        requested_duration,
                        allocation.width,
                        allocation.height
                    ))
                );
        }

        drawingarea.queue_draw();
    }
}
<commit_msg>Fix last timestamp flickering (#52)<commit_after>extern crate cairo;

extern crate gtk;
use gtk::{Inhibit, ToolButtonExt, WidgetExt};

#[cfg(feature = "profiling-audio-draw")]
use chrono::Utc;

use std::boxed::Box;

use std::rc::Rc;
use std::cell::RefCell;

use std::sync::{Arc, Mutex};

use media::{Context, DoubleAudioBuffer, SampleExtractor, Timestamp};

use super::{BACKGROUND_COLOR, ControllerState, DoubleWaveformBuffer, MainController,
            WaveformConditions, WaveformBuffer};

const BUFFER_DURATION:   u64 = 60_000_000_000;    // 60 s
const MIN_REQ_DURATION:  f64 =      1_953_125f64; //  2 ms / 1000 px
const MAX_REQ_DURATION:  f64 = 32_000_000_000f64; // 32 s  / 1000 px
const INIT_REQ_DURATION: f64 =  4_000_000_000f64; //  4 s  / 1000 px
const STEP_REQ_DURATION: f64 =  2f64;

pub struct AudioController {
    container: gtk::Container,
    drawingarea: gtk::DrawingArea,
    zoom_in_btn: gtk::ToolButton,
    zoom_out_btn: gtk::ToolButton,

    is_active: bool,

    requested_duration: Rc<RefCell<f64>>,

    waveform_mtx: Arc<Mutex<Box<SampleExtractor>>>,
    dbl_buffer_mtx: Arc<Mutex<DoubleAudioBuffer>>,
}

impl AudioController {
    pub fn new(builder: &gtk::Builder) -> Self {
        let dbl_buffer_mtx = DoubleWaveformBuffer::new(BUFFER_DURATION);
        let waveform_mtx = dbl_buffer_mtx.lock()
            .expect("AudioController::new: couldn't lock dbl_buffer_mtx")
            .get_exposed_buffer_mtx();

        AudioController {
            container: builder.get_object("audio-container").unwrap(),
            drawingarea: builder.get_object("audio-drawingarea").unwrap(),
            zoom_in_btn: builder.get_object("audio_zoom_in-toolbutton").unwrap(),
            zoom_out_btn: builder.get_object("audio_zoom_out-toolbutton").unwrap(),

            is_active: false,
            requested_duration: Rc::new(RefCell::new(INIT_REQ_DURATION)),
            waveform_mtx: waveform_mtx,
            dbl_buffer_mtx: dbl_buffer_mtx,
        }
    }

    pub fn register_callbacks(&self, main_ctrl: &Rc<RefCell<MainController>>) {
        // draw
        let waveform_mtx = Arc::clone(&self.waveform_mtx);
        let requested_duration = Rc::clone(&self.requested_duration);
        self.drawingarea.connect_draw(move |drawing_area, cairo_ctx| {
            AudioController::draw(
                drawing_area,
                cairo_ctx,
                &waveform_mtx,
                *requested_duration.borrow(),
            ).into()
        });

        // widget size changed
        let requested_duration = Rc::clone(&self.requested_duration);
        let dbl_buffer_mtx = Arc::clone(&self.dbl_buffer_mtx);
        self.drawingarea.connect_size_allocate(move |drawingarea, _| {
            AudioController::refresh(
                drawingarea,
                *requested_duration.borrow(),
                &dbl_buffer_mtx,
            );
        });

        // click in drawing_area
        let main_ctrl_rc = Rc::clone(&main_ctrl);
        let waveform_mtx = Arc::clone(&self.waveform_mtx);
        self.drawingarea.connect_button_press_event(move |_, event_button| {
            let button = event_button.get_button();
            if button == 1 {
                if let Some(position) = {
                    let waveform_buffer_grd = &mut *waveform_mtx.lock()
                        .expect("Couldn't lock waveform buffer in audio controller draw");
                    waveform_buffer_grd
                        .as_mut_any().downcast_mut::<WaveformBuffer>()
                        .expect("SamplesExtratctor is not a waveform buffer in audio controller draw")
                        .get_position(event_button.get_position().0)
                }
                {
                    main_ctrl_rc.borrow_mut().seek(position, true); // accurate (slow)
                }
            }
            Inhibit(true)
        });

        // click zoom in
        let drawingarea = self.drawingarea.clone();
        let requested_duration = Rc::clone(&self.requested_duration);
        let dbl_buffer_mtx = Arc::clone(&self.dbl_buffer_mtx);
        self.zoom_in_btn.connect_clicked(move |_| {
            let (can_update, duration) = {
                let mut duration_grd = requested_duration.borrow_mut();
                *duration_grd /= STEP_REQ_DURATION;
                if *duration_grd >= MIN_REQ_DURATION {
                    (true, *duration_grd)
                } else {
                    *duration_grd = MIN_REQ_DURATION;
                    (false, *duration_grd)
                }
            };

            if can_update {
                AudioController::refresh(
                    &drawingarea,
                    duration,
                    &dbl_buffer_mtx,
                );
            }
        });

        // click zoom out
        let drawingarea = self.drawingarea.clone();
        let requested_duration = Rc::clone(&self.requested_duration);
        let dbl_buffer_mtx = Arc::clone(&self.dbl_buffer_mtx);
        self.zoom_out_btn.connect_clicked(move |_| {
            let (can_update, duration) = {
                let mut duration_grd = requested_duration.borrow_mut();
                *duration_grd *= STEP_REQ_DURATION;
                if *duration_grd <= MAX_REQ_DURATION {
                    (true, *duration_grd)
                } else {
                    *duration_grd = MAX_REQ_DURATION;
                    (false, *duration_grd)
                }
            };

            if can_update {
                AudioController::refresh(
                    &drawingarea,
                    duration,
                    &dbl_buffer_mtx,
                );
            }
        });
    }

    pub fn cleanup(&mut self) {
        self.is_active = false;
        {
            self.dbl_buffer_mtx.lock()
                .expect("AudioController::cleanup: Couldn't lock dbl_buffer_mtx")
                .cleanup();
        }
        *self.requested_duration.borrow_mut() = INIT_REQ_DURATION;
        self.drawingarea.queue_draw();
    }

    pub fn get_dbl_buffer_mtx(&self) -> Arc<Mutex<DoubleAudioBuffer>> {
        Arc::clone(&self.dbl_buffer_mtx)
    }

    pub fn new_media(&mut self, context: &Context) {
        let has_audio = context.info.lock()
                .expect("Failed to lock media info while initializing audio controller")
                .audio_best
                .is_some();

        if has_audio {
            self.is_active = true;
            self.container.show();
        } else {
            self.container.hide();
        }
    }

    pub fn seek(&mut self, position: u64, state: &ControllerState) {
        {
            self.waveform_mtx.lock()
                .expect("AudioController::seek: Couldn't lock waveform_mtx")
                .as_mut_any().downcast_mut::<WaveformBuffer>()
                .expect("AudioController::seek: SamplesExtratctor is not a WaveformBuffer")
                .seek(position, *state == ControllerState::Playing);
        }

        if *state == ControllerState::Paused {
            // refresh the buffer in order to render the waveform
            // with samples that might not be rendered in current WaveformImage yet
            self.dbl_buffer_mtx.lock()
                .expect("AudioController::seek: couldn't lock dbl_buffer_mtx")
                .refresh();
        }
        self.tick();
    }

    pub fn tick(&self) {
        if self.is_active {
            self.drawingarea.queue_draw();
        }
    }

    fn clean_cairo_context(cr: &cairo::Context) {
        cr.set_source_rgb(
            BACKGROUND_COLOR.0,
            BACKGROUND_COLOR.1,
            BACKGROUND_COLOR.2
        );
        cr.paint();
    }

    fn draw(
        drawingarea: &gtk::DrawingArea,
        cr: &cairo::Context,
        waveform_mtx: &Arc<Mutex<Box<SampleExtractor>>>,
        requested_duration: f64,
    ) -> Inhibit {
        #[cfg(feature = "profiling-audio-draw")]
        let before_init = Utc::now();

        let allocation = drawingarea.get_allocation();
        if allocation.width.is_negative() {
            println!("negative allocation.width: {}", allocation.width);
            return Inhibit(true);
        }

        #[cfg(feature = "profiling-audio-draw")]
        let before_lock = Utc::now();
        #[cfg(feature = "profiling-audio-draw")]
        let mut _before_cndt = Utc::now();
        #[cfg(feature = "profiling-audio-draw")]
        let mut _before_image = Utc::now();

        let (first_pos, last_opt, cursor_opt) = {
            let waveform_grd = &mut *waveform_mtx.lock()
                .expect("AudioController::draw: couldn't lock waveform_mtx");
            let waveform_buffer = waveform_grd
                .as_mut_any().downcast_mut::<WaveformBuffer>()
                .expect("AudioController::draw: SamplesExtratctor is not a WaveformBuffer");

            #[cfg(feature = "profiling-audio-draw")]
            let _before_cndt = Utc::now();

            waveform_buffer.update_conditions(
                requested_duration,
                allocation.width,
                allocation.height
            );

            match waveform_buffer.get_image() {
                Some((image, x_offset, first_pos, last_opt, cursor_opt)) => {
                    #[cfg(feature = "profiling-audio-draw")]
                    let _before_image = Utc::now();

                    cr.set_source_surface(image, -x_offset, 0f64);
                    cr.paint();

                    (first_pos, last_opt, cursor_opt)
                },
                None => {
                    AudioController::clean_cairo_context(cr);
                    return Inhibit(true)
                },
            }
        };

        #[cfg(feature = "profiling-audio-draw")]
        let before_pos = Utc::now();

        let height = f64::from(allocation.height);
        let width = f64::from(allocation.width);
        cr.scale(1f64, 1f64);
        cr.set_source_rgb(1f64, 1f64, 0f64);
        cr.set_font_size(14f64);

        // first position
        let first_text = format!("{}", Timestamp::format(first_pos, false));
        let first_text_width = 2f64 + cr.text_extents(&first_text).width;
        cr.move_to(2f64, 30f64);
        cr.show_text(&first_text);

        // last position
        if let Some((last_x, last_pos)) = last_opt {
            let last_text = format!("{}", Timestamp::format(last_pos, false));
            // align to a 5px multiple box in order to avoid flickering
            let last_text_width =
                (cr.text_extents(&last_text).width / 5f64).ceil() * 5f64;
            if last_x + last_text_width > first_text_width + 10f64 {
                // last text won't overlap with first text
                cr.move_to(last_x - last_text_width, 30f64);
                cr.show_text(&last_text);
            }
        }

        if let Some((current_x, current_pos)) = cursor_opt {
            // draw current pos
            let cursor_text = format!("{}", Timestamp::format(current_pos, true));
            let cursor_text_width = 5f64 + cr.text_extents(&cursor_text).width;
            let cursor_text_x =
                if current_x + cursor_text_width < width {
                    current_x + 5f64
                } else {
                    current_x - cursor_text_width
                };
            cr.move_to(cursor_text_x, 15f64);
            cr.show_text(&cursor_text);

            cr.set_line_width(1f64);
            cr.move_to(current_x, 0f64);
            cr.line_to(current_x, height);
            cr.stroke();
        }

        #[cfg(feature = "profiling-audio-draw")]
        let end = Utc::now();

        #[cfg(feature = "profiling-audio-draw")]
        println!("audio-draw,{},{},{},{},{},{}",
            before_init.time().format("%H:%M:%S%.6f"),
            before_lock.time().format("%H:%M:%S%.6f"),
            _before_cndt.time().format("%H:%M:%S%.6f"),
            _before_image.time().format("%H:%M:%S%.6f"),
            before_pos.time().format("%H:%M:%S%.6f"),
            end.time().format("%H:%M:%S%.6f"),
        );

       Inhibit(true)
    }

    fn refresh(
        drawingarea: &gtk::DrawingArea,
        requested_duration: f64,
        dbl_buffer_mtx: &Arc<Mutex<DoubleAudioBuffer>>,
    ) {
        let allocation = drawingarea.get_allocation();
        {
            // refresh the buffer in order to render the waveform
            // in latest conditions
            dbl_buffer_mtx.lock()
                .expect("AudioController::size-allocate: couldn't lock dbl_buffer_mtx")
                .refresh_with_conditions(
                    Box::new(WaveformConditions::new(
                        requested_duration,
                        allocation.width,
                        allocation.height
                    ))
                );
        }

        drawingarea.queue_draw();
    }
}
<|endoftext|>


<commit_before>extern crate gtk;
extern crate cairo;

extern crate ffmpeg;

use std::ops::{Deref, DerefMut};

use std::rc::Rc;
use std::cell::RefCell;

use gtk::prelude::*;
use cairo::enums::{FontSlant, FontWeight};

use ffmpeg::format::stream::disposition::ATTACHED_PIC;

use ::media::Context;
use ::media::PacketNotifiable;

use super::MediaNotifiable;
use super::MediaController;

fn ffmpeg_pixel_format_to_cairo(ffmpeg_px: ffmpeg::format::Pixel) -> cairo::Format {
    match ffmpeg_px {
        ffmpeg::format::Pixel::ARGB => cairo::Format::ARgb32,
        ffmpeg::format::Pixel::RGB24 => cairo::Format::Rgb24,
        ffmpeg::format::Pixel::RGB565LE => cairo::Format::Rgb16_565,
        _ => cairo::Format::Invalid,
    }
}

pub struct VideoController {
    media_ctl: MediaController,
    drawingarea: gtk::DrawingArea,
    message: String,
    frame: Option<ffmpeg::frame::Video>,
}

impl VideoController {
    pub fn new(builder: &gtk::Builder) -> Rc<RefCell<VideoController>> {
        // need a RefCell because the callbacks will use immutable versions of vc
        // when the UI controllers will get a mutable version from time to time
        let vc = Rc::new(RefCell::new(VideoController {
            media_ctl: MediaController::new(builder.get_object("video-container").unwrap()),
            drawingarea: builder.get_object("video-drawingarea").unwrap(),
            message: "video place holder".to_owned(),
            frame: None,
        }));

        let vc_for_cb = vc.clone();
        vc.borrow().drawingarea.connect_draw(move |_, cairo_ctx| {
            vc_for_cb.borrow().draw(&cairo_ctx);
            Inhibit(false)
        });

        vc
    }

    fn convert_to_rgb(&mut self, frame_in: &mut ffmpeg::frame::Video, time_base:
                      ffmpeg::Rational) -> Result<(ffmpeg::frame::Video), String> {
        let mut graph = ffmpeg::filter::Graph::new();
        let in_filter;
        match ffmpeg::filter::find("buffer") {
            Some(value) => in_filter = value,
            None => return Err("Couldn't get buffer".to_owned()),
        }

        // Fix deprecated formats
        let in_format = match frame_in.format() {
            ffmpeg::format::Pixel::YUVJ420P => ffmpeg::format::Pixel::YUV420P,
            ffmpeg::format::Pixel::YUVJ422P => ffmpeg::format::Pixel::YUV422P,
            ffmpeg::format::Pixel::YUVJ444P => ffmpeg::format::Pixel::YUV444P,
            ffmpeg::format::Pixel::YUVJ440P => ffmpeg::format::Pixel::YUV440P,
            other => other,
        };
        frame_in.set_format(in_format);
        let in_format_str = format!("{:?}", in_format).to_lowercase();
        let args = format!("width={}:height={}:pix_fmt={}:time_base={}:pixel_aspect={}",
                           frame_in.width(), frame_in.height(), in_format_str,
                           time_base, frame_in.aspect_ratio());
        println!("pad in args: {}", args);
        match graph.add(&in_filter, "in", &args) {
            Ok(_) => (),
            Err(error) => return Err(format!("Error adding in pad: {:?}", error)),
        }

        let out_filter;
        match ffmpeg::filter::find("buffersink") {
            Some(value) => out_filter = value,
            None => return Err("Couldn't get buffersink".to_owned()),
        }
        match graph.add(&out_filter, "out", "") {
            Ok(_) => (),
            Err(error) => return Err(format!("Error adding out pad: {:?}", error)),
        }
        {
            let mut out_pad = graph.get("out").unwrap();
            out_pad.set_pixel_format(ffmpeg::format::Pixel::RGB565LE);
        }

        {
            let in_parser;
            match graph.output("in", 0) {
                Ok(value) => in_parser = value,
                Err(error) => return Err(format!("Error getting output for in pad: {:?}", error)),
            }
            let out_parser;
            match in_parser.input("out", 0) {
                Ok(value) => out_parser = value,
                Err(error) => return Err(format!("Error getting input for out pad: {:?}", error)),
            }
            match out_parser.parse("copy") {
                Ok(_) => (),
                Err(error) => return Err(format!("Error parsing format: {:?}", error)),
            }
        }

        match graph.validate() {
            Ok(_) => (),
            Err(error) => return Err(format!("Error validating graph: {:?}", error)),
        }

        //println!("{}", graph.dump());

        graph.get("in").unwrap().source().add(&frame_in).unwrap();

        let mut frame_rgb = ffmpeg::frame::Video::empty();
		while let Ok(..) = graph.get("out").unwrap().sink().frame(&mut frame_rgb) {
        }

        Ok(frame_rgb)
    }

    fn draw(&self, cr: &cairo::Context) {
        let allocation = self.drawingarea.get_allocation();

        match self.frame {
            Some(ref frame) => {
                let planes = frame.planes();
                if planes > 0 {
                    /*
                    println!("format: {:?}, width: {}, stride: {}",
                             frame.format(), frame.width(), frame.stride(0));
                    let test_surface = cairo::ImageSurface::create(
                            ffmpeg_pixel_format_to_cairo(frame.format()),
                            frame.width() as i32, frame.height() as i32
                        );
                    println!("expected stride: {}", test_surface.get_stride());
                    */

                    let surface = cairo::ImageSurface::create_for_data(
                            frame.data(0).to_vec().into_boxed_slice(), |_| {},
                            ffmpeg_pixel_format_to_cairo(frame.format()),
                            frame.width() as i32, frame.height() as i32,
                            frame.stride(0) as i32
                        );

                    let scale;
                    let alloc_ratio = allocation.width as f64 / allocation.height as f64;
                    let surface_ratio = surface.get_width() as f64 / surface.get_height() as f64;
                    if surface_ratio < alloc_ratio {
                        scale = allocation.height as f64 / surface.get_height() as f64;
                    }
                    else {
                        scale = allocation.width as f64 / surface.get_width() as f64;
                    }
                    let x = (allocation.width as f64 / scale - surface.get_width() as f64).abs() / 2f64;
                    let y = (allocation.height as f64 / scale - surface.get_height() as f64).abs() / 2f64;

                    cr.scale(scale, scale);
                    cr.set_source_surface(&surface, x, y);
                    cr.paint();
                }
            },
            None => {
                cr.scale(allocation.width as f64, allocation.height as f64);
                cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Normal);
                cr.set_font_size(0.07);

                cr.move_to(0.1, 0.53);
                cr.show_text(&self.message);
            },
        }
    }
}

impl Deref for VideoController {
	type Target = MediaController;

	fn deref(&self) -> &Self::Target {
		&self.media_ctl
	}
}

impl DerefMut for VideoController {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.media_ctl
	}
}

impl MediaNotifiable for VideoController {
    fn new_media(&mut self, context: &mut Context) {
        self.frame = None;
        self.message = match context.video_stream.as_mut() {
            Some(stream) => {
                self.set_index(stream.index);

                self.show();
                println!("\n** Video stream\n{:?}", &stream);

                let stream_type;
                if stream.disposition | ATTACHED_PIC == ATTACHED_PIC {
                    stream_type = "image";
                }
                else {
                    stream_type = "video stream";
                }
                format!("{} {}", stream_type, self.stream_index().unwrap())
            },
            None => {
                self.hide();
                "no video stream".to_owned()
            },
        };

        self.drawingarea.queue_draw();
    }
}

impl PacketNotifiable for VideoController {
    fn new_packet(&mut self, stream: &ffmpeg::format::stream::Stream, packet: &ffmpeg::codec::packet::Packet) {
        self.print_packet_content(stream, packet);

        let decoder = stream.codec().decoder();
        match decoder.video() {
            Ok(mut video) => {
                let mut frame = ffmpeg::frame::Video::new(video.format(), video.width(), video.height());
                match video.decode(packet, &mut frame) {
                    Ok(result) => if result {
                            let planes = frame.planes();
                            if planes > 0 {
                                println!("\tdecoded video frame, data len: {}", frame.data(0).len());

                                match self.convert_to_rgb(&mut frame, video.time_base()) {
                                    Ok(frame_rgb) => {
                                        self.frame = Some(frame_rgb);
                                    }
                                    Err(error) =>  println!("\tError converting to rgb: {:?}", error),
                                }
                            }
                            else {
                                println!("\tno planes found in frame");
                            }
                        }
                        else {
                            println!("\tfailed to decode video frame");
                        }
                    ,
                    Err(error) => println!("Error decoding video: {:?}", error),
                }
            },
            Err(error) => println!("Error getting video decoder: {:?}", error),
        }
    }
}
<commit_msg>Video: reuse filter graph (#1)<commit_after>extern crate gtk;
extern crate cairo;

extern crate ffmpeg;

use std::ops::{Deref, DerefMut};

use std::rc::Rc;
use std::cell::RefCell;

use gtk::prelude::*;
use cairo::enums::{FontSlant, FontWeight};

use ffmpeg::format::stream::disposition::ATTACHED_PIC;

use ::media::Context;
use ::media::PacketNotifiable;

use super::MediaNotifiable;
use super::MediaController;

fn ffmpeg_pixel_format_to_cairo(ffmpeg_px: ffmpeg::format::Pixel) -> cairo::Format {
    match ffmpeg_px {
        ffmpeg::format::Pixel::ARGB => cairo::Format::ARgb32,
        ffmpeg::format::Pixel::RGB24 => cairo::Format::Rgb24,
        ffmpeg::format::Pixel::RGB565LE => cairo::Format::Rgb16_565,
        _ => cairo::Format::Invalid,
    }
}

pub struct VideoController {
    media_ctl: MediaController,
    drawingarea: gtk::DrawingArea,
    message: String,
    frame: Option<ffmpeg::frame::Video>,
    graph: Option<ffmpeg::filter::Graph>,
}

impl VideoController {
    pub fn new(builder: &gtk::Builder) -> Rc<RefCell<VideoController>> {
        // need a RefCell because the callbacks will use immutable versions of vc
        // when the UI controllers will get a mutable version from time to time
        let vc = Rc::new(RefCell::new(VideoController {
            media_ctl: MediaController::new(builder.get_object("video-container").unwrap()),
            drawingarea: builder.get_object("video-drawingarea").unwrap(),
            message: "video place holder".to_owned(),
            frame: None,
            graph: None,
        }));

        let vc_for_cb = vc.clone();
        vc.borrow().drawingarea.connect_draw(move |_, cairo_ctx| {
            vc_for_cb.borrow().draw(&cairo_ctx);
            Inhibit(false)
        });

        vc
    }

    fn build_graph(&mut self, frame_in: &mut ffmpeg::frame::Video,
                 time_base: ffmpeg::Rational) -> Result<bool, String> { // TODO: check how to return Ok() only
        match self.graph {
            Some(_) => (),
            None => {
                let mut graph = ffmpeg::filter::Graph::new();

                // Fix deprecated formats
                let in_format = match frame_in.format() {
                    ffmpeg::format::Pixel::YUVJ420P => ffmpeg::format::Pixel::YUV420P,
                    ffmpeg::format::Pixel::YUVJ422P => ffmpeg::format::Pixel::YUV422P,
                    ffmpeg::format::Pixel::YUVJ444P => ffmpeg::format::Pixel::YUV444P,
                    ffmpeg::format::Pixel::YUVJ440P => ffmpeg::format::Pixel::YUV440P,
                    other => other,
                };
                frame_in.set_format(in_format);
                let in_format_str = format!("{:?}", in_format).to_lowercase();
                let args = format!("width={}:height={}:pix_fmt={}:time_base={}:pixel_aspect={}",
                                   frame_in.width(), frame_in.height(), in_format_str,
                                   time_base, frame_in.aspect_ratio());

                let in_filter = ffmpeg::filter::find("buffer").unwrap();
                match graph.add(&in_filter, "in", &args) {
                    Ok(_) => (),
                    Err(error) => return Err(format!("Error adding in pad: {:?}", error)),
                }

                let out_filter = ffmpeg::filter::find("buffersink").unwrap();
                match graph.add(&out_filter, "out", "") {
                    Ok(_) => (),
                    Err(error) => return Err(format!("Error adding out pad: {:?}", error)),
                }
                {
                    let mut out_pad = graph.get("out").unwrap();
                    out_pad.set_pixel_format(ffmpeg::format::Pixel::RGB565LE);
                }

                {
                    let in_parser;
                    match graph.output("in", 0) {
                        Ok(value) => in_parser = value,
                        Err(error) => return Err(format!("Error getting output for in pad: {:?}", error)),
                    }
                    let out_parser;
                    match in_parser.input("out", 0) {
                        Ok(value) => out_parser = value,
                        Err(error) => return Err(format!("Error getting input for out pad: {:?}", error)),
                    }
                    match out_parser.parse("copy") {
                        Ok(_) => (),
                        Err(error) => return Err(format!("Error parsing format: {:?}", error)),
                    }
                }

                match graph.validate() {
                    Ok(_) => self.graph = Some(graph),
                    Err(error) => return Err(format!("Error validating graph: {:?}", error)),
                }

                //println!("{}", graph.dump());
            },
        }

        Ok(true)
    }

    fn convert_to_rgb(&mut self, frame_in: &mut ffmpeg::frame::Video,
                      time_base: ffmpeg::Rational) -> Result<ffmpeg::frame::Video, String> {
        match self.build_graph(frame_in, time_base) {
            Ok(_) => {
                let mut graph = self.graph.as_mut().unwrap();
                graph.get("in").unwrap().source().add(&frame_in).unwrap();

                let mut frame_rgb = ffmpeg::frame::Video::empty();
                while let Ok(..) = graph.get("out").unwrap().sink().frame(&mut frame_rgb) {
                }

                Ok(frame_rgb)
            },
            Err(error) => Err(error),
        }
    }

    fn draw(&self, cr: &cairo::Context) {
        let allocation = self.drawingarea.get_allocation();

        match self.frame {
            Some(ref frame) => {
                let planes = frame.planes();
                if planes > 0 {
                    /*
                    println!("format: {:?}, width: {}, stride: {}",
                             frame.format(), frame.width(), frame.stride(0));
                    let test_surface = cairo::ImageSurface::create(
                            ffmpeg_pixel_format_to_cairo(frame.format()),
                            frame.width() as i32, frame.height() as i32
                        );
                    println!("expected stride: {}", test_surface.get_stride());
                    */

                    let surface = cairo::ImageSurface::create_for_data(
                            frame.data(0).to_vec().into_boxed_slice(), |_| {},
                            ffmpeg_pixel_format_to_cairo(frame.format()),
                            frame.width() as i32, frame.height() as i32,
                            frame.stride(0) as i32
                        );

                    let scale;
                    let alloc_ratio = allocation.width as f64 / allocation.height as f64;
                    let surface_ratio = surface.get_width() as f64 / surface.get_height() as f64;
                    if surface_ratio < alloc_ratio {
                        scale = allocation.height as f64 / surface.get_height() as f64;
                    }
                    else {
                        scale = allocation.width as f64 / surface.get_width() as f64;
                    }
                    let x = (allocation.width as f64 / scale - surface.get_width() as f64).abs() / 2f64;
                    let y = (allocation.height as f64 / scale - surface.get_height() as f64).abs() / 2f64;

                    cr.scale(scale, scale);
                    cr.set_source_surface(&surface, x, y);
                    cr.paint();
                }
            },
            None => {
                cr.scale(allocation.width as f64, allocation.height as f64);
                cr.select_font_face("Sans", FontSlant::Normal, FontWeight::Normal);
                cr.set_font_size(0.07);

                cr.move_to(0.1, 0.53);
                cr.show_text(&self.message);
            },
        }
    }
}

impl Deref for VideoController {
	type Target = MediaController;

	fn deref(&self) -> &Self::Target {
		&self.media_ctl
	}
}

impl DerefMut for VideoController {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.media_ctl
	}
}

impl MediaNotifiable for VideoController {
    fn new_media(&mut self, context: &mut Context) {
        self.frame = None;
        self.graph = None;
        self.message = match context.video_stream.as_mut() {
            Some(stream) => {
                self.set_index(stream.index);

                self.show();
                println!("\n** Video stream\n{:?}", &stream);

                let stream_type;
                if stream.disposition | ATTACHED_PIC == ATTACHED_PIC {
                    stream_type = "image";
                }
                else {
                    stream_type = "video stream";
                }
                format!("{} {}", stream_type, self.stream_index().unwrap())
            },
            None => {
                self.hide();
                "no video stream".to_owned()
            },
        };

        self.drawingarea.queue_draw();
    }
}

impl PacketNotifiable for VideoController {
    fn new_packet(&mut self, stream: &ffmpeg::format::stream::Stream, packet: &ffmpeg::codec::packet::Packet) {
        self.print_packet_content(stream, packet);

        let decoder = stream.codec().decoder();
        match decoder.video() {
            Ok(mut video) => {
                let mut frame = ffmpeg::frame::Video::new(video.format(), video.width(), video.height());
                match video.decode(packet, &mut frame) {
                    Ok(result) => if result {
                            let planes = frame.planes();
                            if planes > 0 {
                                println!("\tdecoded video frame, data len: {}", frame.data(0).len());

                                match self.convert_to_rgb(&mut frame, video.time_base()) {
                                    Ok(frame_rgb) => {
                                        self.frame = Some(frame_rgb);
                                    }
                                    Err(error) =>  println!("\tError converting to rgb: {:?}", error),
                                }
                            }
                            else {
                                println!("\tno planes found in frame");
                            }
                        }
                        else {
                            println!("\tfailed to decode video frame");
                        }
                    ,
                    Err(error) => println!("Error decoding video: {:?}", error),
                }
            },
            Err(error) => println!("Error getting video decoder: {:?}", error),
        }
    }
}
<|endoftext|>