<commit_before>extern crate "cargo-registry" as cargo_registry;
extern crate migrate;
extern crate postgres;
extern crate r2d2;

use std::os;
use migrate::Migration;
use postgres::{PostgresTransaction, PostgresResult};

use cargo_registry::krate::Crate;

fn main() {
    let db_config = r2d2::Config {
        pool_size: 1,
        helper_tasks: 1,
        test_on_check_out: false,
    };
    let database = cargo_registry::db::pool(env("DATABASE_URL").as_slice(),
                                            db_config);
    let conn = database.get().unwrap();
    let migrations = migrations();

    if os::args().as_slice().get(1).map(|s| s.as_slice()) == Some("rollback") {
        rollback(conn.transaction().unwrap(), migrations).unwrap();
    } else {
        apply(conn.transaction().unwrap(), migrations).unwrap();
    }

    fn env(s: &str) -> String {
        match os::getenv(s) {
            Some(s) => s,
            None => fail!("must have `{}` defined", s),
        }
    }
}

fn apply(tx: PostgresTransaction,
         migrations: Vec<Migration>) -> PostgresResult<()> {
    let mut mgr = try!(migrate::Manager::new(tx));
    for m in migrations.into_iter() {
        try!(mgr.apply(m));
    }
    mgr.set_commit();
    mgr.finish()
}

fn rollback(tx: PostgresTransaction,
            migrations: Vec<Migration>) -> PostgresResult<()> {
    let mut mgr = try!(migrate::Manager::new(tx));
    for m in migrations.into_iter().rev() {
        if mgr.contains(m.version()) {
            try!(mgr.rollback(m));
            break
        }
    }
    mgr.set_commit();
    mgr.finish()
}

fn migrations() -> Vec<Migration> {
    // Generate a new id via `date +"%Y%m%d%H%M%S"`
    vec![
        Migration::add_table(20140924113530, "users", "
            id              SERIAL PRIMARY KEY,
            email           VARCHAR NOT NULL UNIQUE,
            gh_access_token VARCHAR NOT NULL,
            api_token       VARCHAR NOT NULL
        "),
        Migration::add_table(20140924114003, "packages", "
            id              SERIAL PRIMARY KEY,
            name            VARCHAR NOT NULL UNIQUE,
            user_id         INTEGER NOT NULL
        "),
        Migration::add_table(20140924114059, "versions", "
            id              SERIAL PRIMARY KEY,
            package_id      INTEGER NOT NULL,
            num             VARCHAR NOT NULL
        "),
        Migration::run(20140924115329,
                       format!("ALTER TABLE versions ADD CONSTRAINT \
                                unique_num UNIQUE (package_id, num)"),
                       format!("ALTER TABLE versions DROP CONSTRAINT \
                                unique_num")),
        Migration::add_table(20140924120803, "version_dependencies", "
            version_id      INTEGER NOT NULL,
            depends_on_id   INTEGER NOT NULL
        "),
        Migration::add_column(20140925132248, "packages", "updated_at",
                              "TIMESTAMP NOT NULL DEFAULT now()"),
        Migration::add_column(20140925132249, "packages", "created_at",
                              "TIMESTAMP NOT NULL DEFAULT now()"),
        Migration::new(20140925132250, proc(tx) {
            try!(tx.execute("UPDATE packages SET updated_at = now() \
                             WHERE updated_at IS NULL", []));
            try!(tx.execute("UPDATE packages SET created_at = now() \
                             WHERE created_at IS NULL", []));
            Ok(())
        }, proc(_) Ok(())),
        Migration::add_column(20140925132251, "versions", "updated_at",
                              "TIMESTAMP NOT NULL DEFAULT now()"),
        Migration::add_column(20140925132252, "versions", "created_at",
                              "TIMESTAMP NOT NULL DEFAULT now()"),
        Migration::new(20140925132253, proc(tx) {
            try!(tx.execute("UPDATE versions SET updated_at = now() \
                             WHERE updated_at IS NULL", []));
            try!(tx.execute("UPDATE versions SET created_at = now() \
                             WHERE created_at IS NULL", []));
            Ok(())
        }, proc(_) Ok(())),
        Migration::new(20140925132254, proc(tx) {
            try!(tx.execute("ALTER TABLE versions ALTER COLUMN updated_at \
                             DROP DEFAULT", []));
            try!(tx.execute("ALTER TABLE versions ALTER COLUMN created_at \
                             DROP DEFAULT", []));
            try!(tx.execute("ALTER TABLE packages ALTER COLUMN updated_at \
                             DROP DEFAULT", []));
            try!(tx.execute("ALTER TABLE packages ALTER COLUMN created_at \
                             DROP DEFAULT", []));
            Ok(())
        }, proc(_) Ok(())),
        Migration::add_table(20140925153704, "metadata", "
            total_downloads        BIGINT NOT NULL
        "),
        Migration::new(20140925153705, proc(tx) {
            try!(tx.execute("INSERT INTO metadata (total_downloads) \
                             VALUES ($1)", &[&0i64]));
            Ok(())
        }, proc(tx) {
            try!(tx.execute("DELETE FROM metadata", [])); Ok(())
        }),
        Migration::add_column(20140925161623, "packages", "downloads",
                              "INTEGER NOT NULL DEFAULT 0"),
        Migration::add_column(20140925161624, "versions", "downloads",
                              "INTEGER NOT NULL DEFAULT 0"),
        Migration::new(20140925161625, proc(tx) {
            try!(tx.execute("ALTER TABLE versions ALTER COLUMN downloads \
                             DROP DEFAULT", []));
            try!(tx.execute("ALTER TABLE packages ALTER COLUMN downloads \
                             DROP DEFAULT", []));
            Ok(())
        }, proc(_) Ok(())),
        Migration::add_column(20140926130044, "packages", "max_version",
                              "VARCHAR"),
        Migration::new(20140926130045, proc(tx) {
            let stmt = try!(tx.prepare("SELECT * FROM packages"));
            for row in try!(stmt.query(&[])) {
                let pkg = Crate::from_row(&row);
                let versions = pkg.versions(tx).unwrap();
                let v = versions.iter().max_by(|v| &v.num).unwrap();
                let max = v.num.to_string();
                try!(tx.execute("UPDATE packages SET max_version = $1 \
                                 WHERE id = $2",
                                &[&max, &pkg.id]));
            }
            Ok(())
        }, proc(_) Ok(())),
        Migration::new(20140926130046, proc(tx) {
            try!(tx.execute("ALTER TABLE versions ALTER COLUMN downloads \
                             SET NOT NULL", []));
            Ok(())
        }, proc(tx) {
            try!(tx.execute("ALTER TABLE versions ALTER COLUMN downloads \
                             DROP NOT NULL", []));
            Ok(())
        }),
        Migration::new(20140926174020, proc(tx) {
            try!(tx.execute("ALTER TABLE packages RENAME TO crates", []));
            try!(tx.execute("ALTER TABLE versions RENAME COLUMN package_id \
                             TO crate_id", []));
            Ok(())
        }, proc(tx) {
            try!(tx.execute("ALTER TABLE crates RENAME TO packages", []));
            try!(tx.execute("ALTER TABLE versions RENAME COLUMN crate_id \
                             TO package_id", []));
            Ok(())
        }),
        Migration::run(20140929103749,
                       "CREATE INDEX crate_updated_at ON crates (updated_at)",
                       "DROP INDEX crate_updated_at"),
        Migration::run(20140929103750,
                       "CREATE INDEX crate_created_at ON crates (created_at)",
                       "DROP INDEX crate_created_at"),
        Migration::run(20140929103751,
                       "CREATE INDEX crate_downloads ON crates (downloads)",
                       "DROP INDEX crate_downloads"),
        Migration::run(20140929103752,
                       "CREATE INDEX version_crate_id ON versions (crate_id)",
                       "DROP INDEX version_crate_id"),
        Migration::run(20140929103753,
                       "CREATE INDEX version_num ON versions (num)",
                       "DROP INDEX version_num"),
    ]
}
<commit_msg>Index columns of version_dependencies<commit_after>extern crate "cargo-registry" as cargo_registry;
extern crate migrate;
extern crate postgres;
extern crate r2d2;

use std::os;
use migrate::Migration;
use postgres::{PostgresTransaction, PostgresResult};

use cargo_registry::krate::Crate;

fn main() {
    let db_config = r2d2::Config {
        pool_size: 1,
        helper_tasks: 1,
        test_on_check_out: false,
    };
    let database = cargo_registry::db::pool(env("DATABASE_URL").as_slice(),
                                            db_config);
    let conn = database.get().unwrap();
    let migrations = migrations();

    if os::args().as_slice().get(1).map(|s| s.as_slice()) == Some("rollback") {
        rollback(conn.transaction().unwrap(), migrations).unwrap();
    } else {
        apply(conn.transaction().unwrap(), migrations).unwrap();
    }

    fn env(s: &str) -> String {
        match os::getenv(s) {
            Some(s) => s,
            None => fail!("must have `{}` defined", s),
        }
    }
}

fn apply(tx: PostgresTransaction,
         migrations: Vec<Migration>) -> PostgresResult<()> {
    let mut mgr = try!(migrate::Manager::new(tx));
    for m in migrations.into_iter() {
        try!(mgr.apply(m));
    }
    mgr.set_commit();
    mgr.finish()
}

fn rollback(tx: PostgresTransaction,
            migrations: Vec<Migration>) -> PostgresResult<()> {
    let mut mgr = try!(migrate::Manager::new(tx));
    for m in migrations.into_iter().rev() {
        if mgr.contains(m.version()) {
            try!(mgr.rollback(m));
            break
        }
    }
    mgr.set_commit();
    mgr.finish()
}

fn migrations() -> Vec<Migration> {
    // Generate a new id via `date +"%Y%m%d%H%M%S"`
    vec![
        Migration::add_table(20140924113530, "users", "
            id              SERIAL PRIMARY KEY,
            email           VARCHAR NOT NULL UNIQUE,
            gh_access_token VARCHAR NOT NULL,
            api_token       VARCHAR NOT NULL
        "),
        Migration::add_table(20140924114003, "packages", "
            id              SERIAL PRIMARY KEY,
            name            VARCHAR NOT NULL UNIQUE,
            user_id         INTEGER NOT NULL
        "),
        Migration::add_table(20140924114059, "versions", "
            id              SERIAL PRIMARY KEY,
            package_id      INTEGER NOT NULL,
            num             VARCHAR NOT NULL
        "),
        Migration::run(20140924115329,
                       format!("ALTER TABLE versions ADD CONSTRAINT \
                                unique_num UNIQUE (package_id, num)"),
                       format!("ALTER TABLE versions DROP CONSTRAINT \
                                unique_num")),
        Migration::add_table(20140924120803, "version_dependencies", "
            version_id      INTEGER NOT NULL,
            depends_on_id   INTEGER NOT NULL
        "),
        Migration::add_column(20140925132248, "packages", "updated_at",
                              "TIMESTAMP NOT NULL DEFAULT now()"),
        Migration::add_column(20140925132249, "packages", "created_at",
                              "TIMESTAMP NOT NULL DEFAULT now()"),
        Migration::new(20140925132250, proc(tx) {
            try!(tx.execute("UPDATE packages SET updated_at = now() \
                             WHERE updated_at IS NULL", []));
            try!(tx.execute("UPDATE packages SET created_at = now() \
                             WHERE created_at IS NULL", []));
            Ok(())
        }, proc(_) Ok(())),
        Migration::add_column(20140925132251, "versions", "updated_at",
                              "TIMESTAMP NOT NULL DEFAULT now()"),
        Migration::add_column(20140925132252, "versions", "created_at",
                              "TIMESTAMP NOT NULL DEFAULT now()"),
        Migration::new(20140925132253, proc(tx) {
            try!(tx.execute("UPDATE versions SET updated_at = now() \
                             WHERE updated_at IS NULL", []));
            try!(tx.execute("UPDATE versions SET created_at = now() \
                             WHERE created_at IS NULL", []));
            Ok(())
        }, proc(_) Ok(())),
        Migration::new(20140925132254, proc(tx) {
            try!(tx.execute("ALTER TABLE versions ALTER COLUMN updated_at \
                             DROP DEFAULT", []));
            try!(tx.execute("ALTER TABLE versions ALTER COLUMN created_at \
                             DROP DEFAULT", []));
            try!(tx.execute("ALTER TABLE packages ALTER COLUMN updated_at \
                             DROP DEFAULT", []));
            try!(tx.execute("ALTER TABLE packages ALTER COLUMN created_at \
                             DROP DEFAULT", []));
            Ok(())
        }, proc(_) Ok(())),
        Migration::add_table(20140925153704, "metadata", "
            total_downloads        BIGINT NOT NULL
        "),
        Migration::new(20140925153705, proc(tx) {
            try!(tx.execute("INSERT INTO metadata (total_downloads) \
                             VALUES ($1)", &[&0i64]));
            Ok(())
        }, proc(tx) {
            try!(tx.execute("DELETE FROM metadata", [])); Ok(())
        }),
        Migration::add_column(20140925161623, "packages", "downloads",
                              "INTEGER NOT NULL DEFAULT 0"),
        Migration::add_column(20140925161624, "versions", "downloads",
                              "INTEGER NOT NULL DEFAULT 0"),
        Migration::new(20140925161625, proc(tx) {
            try!(tx.execute("ALTER TABLE versions ALTER COLUMN downloads \
                             DROP DEFAULT", []));
            try!(tx.execute("ALTER TABLE packages ALTER COLUMN downloads \
                             DROP DEFAULT", []));
            Ok(())
        }, proc(_) Ok(())),
        Migration::add_column(20140926130044, "packages", "max_version",
                              "VARCHAR"),
        Migration::new(20140926130045, proc(tx) {
            let stmt = try!(tx.prepare("SELECT * FROM packages"));
            for row in try!(stmt.query(&[])) {
                let pkg = Crate::from_row(&row);
                let versions = pkg.versions(tx).unwrap();
                let v = versions.iter().max_by(|v| &v.num).unwrap();
                let max = v.num.to_string();
                try!(tx.execute("UPDATE packages SET max_version = $1 \
                                 WHERE id = $2",
                                &[&max, &pkg.id]));
            }
            Ok(())
        }, proc(_) Ok(())),
        Migration::new(20140926130046, proc(tx) {
            try!(tx.execute("ALTER TABLE versions ALTER COLUMN downloads \
                             SET NOT NULL", []));
            Ok(())
        }, proc(tx) {
            try!(tx.execute("ALTER TABLE versions ALTER COLUMN downloads \
                             DROP NOT NULL", []));
            Ok(())
        }),
        Migration::new(20140926174020, proc(tx) {
            try!(tx.execute("ALTER TABLE packages RENAME TO crates", []));
            try!(tx.execute("ALTER TABLE versions RENAME COLUMN package_id \
                             TO crate_id", []));
            Ok(())
        }, proc(tx) {
            try!(tx.execute("ALTER TABLE crates RENAME TO packages", []));
            try!(tx.execute("ALTER TABLE versions RENAME COLUMN crate_id \
                             TO package_id", []));
            Ok(())
        }),
        Migration::run(20140929103749,
                       "CREATE INDEX crate_updated_at ON crates (updated_at)",
                       "DROP INDEX crate_updated_at"),
        Migration::run(20140929103750,
                       "CREATE INDEX crate_created_at ON crates (created_at)",
                       "DROP INDEX crate_created_at"),
        Migration::run(20140929103751,
                       "CREATE INDEX crate_downloads ON crates (downloads)",
                       "DROP INDEX crate_downloads"),
        Migration::run(20140929103752,
                       "CREATE INDEX version_crate_id ON versions (crate_id)",
                       "DROP INDEX version_crate_id"),
        Migration::run(20140929103753,
                       "CREATE INDEX version_num ON versions (num)",
                       "DROP INDEX version_num"),
        Migration::run(20140929103754,
                       "CREATE INDEX version_dependencies_version_id \
                        ON version_dependencies (version_id)",
                       "DROP INDEX version_dependencies_version_id"),
        Migration::run(20140929103755,
                       "CREATE INDEX version_dependencies_depends_on_id \
                        ON version_dependencies (depends_on_id)",
                       "DROP INDEX version_dependencies_depends_on_id"),
    ]
}
<|endoftext|>


<commit_before><commit_msg>Remove unused code<commit_after><|endoftext|>


<commit_before>//! byt - io
//!
//! Implements a piece table to abstract over accessing and
//! modifying a file on disk.

// EXTERNS

// LIBRARY INCLUDES
use std::fs::File;
use std::fs::OpenOptions;
use std::io::BufReader;
use std::io::Read;
use std::io;

// SUBMODULES

// LOCAL INCLUDES

#[derive(Debug, Copy, Clone)]
enum SourceFile {
    /// The original file on disk.
    Original,
    /// The append file, held in memory.
    Append
}

/// A Piece stores some information about a part of the
/// modified file.
struct Piece {
    /// The file this piece refers to.
    file : SourceFile,
    /// The offset (in bytes) of the text in this Piece.
    offset : u32,
    /// The length of the text in this Piece.
    length : u32
}

enum Operation {
    Insert,
    Delete
}

/// Records a particular modification of the text.
struct Action {
    op     : Operation,
    start  : u32,
    length : u32
}

/// Implements logical operations on a file that are not written
/// until asked to.
pub struct PieceFile {
    /// The in-memory append string. All edits will refer to bytes
    /// stored here.
    append_file : String,
    /// The underlying file descriptor on the filesystem.
    //file : File,
    /// The total size (in bytes) of the PieceFile.
    length : u32,
    /// The path of the file this PieceFile refers to.
    path : String,
    /// Stores all current Pieces.
    piece_table : Vec<Piece>,
    /// The seekable file reader.
    reader : BufReader<File>,
}

impl PieceFile {
    // #################################
    // P R I V A T E  F U N C T I O N S
    // #################################
    /// Get a Piece at a particular offset.
    fn get_at_offset(&self, offset : u32) -> Option<usize> {
        let mut logical_length = 0;

        for index in 0..self.piece_table.len() {
            let piece = &self.piece_table[index];
            logical_length += piece.length;

            if offset > logical_length {
                continue;
            }

            return Some(index);
        }
        None
    }

    // ###############################
    // P U B L I C  F U N C T I O N S
    // ###############################
    /// Open a new PieceFile. If the file doesn't exist, it is created.
    pub fn open(path : String) -> io::Result<PieceFile> {
        let file = OpenOptions::new()
            .read(true)
            .open(path.clone()).unwrap();

        // Get the length of the file to initialize the first Piece
        let size = file.metadata().unwrap().len() as u32;

        let mut piece_file = PieceFile {
            append_file : String::new(),
            length : size,
            path,
            piece_table : Vec::new(),
            reader : BufReader::new(file),
        };

        piece_file.piece_table.push(Piece {
            file : SourceFile::Original,
            offset : 0,
            length : size as u32,
        });

        Ok(piece_file)
    }

    /// Insert some text.
    pub fn insert(&mut self, text : &str, offset : u32) {
        let length = text.len() as u32;
        let append_offset = self.append_file.len() as u32;

        self.append_file += text;
        self.length      += length;

        let piece = Piece {
            file : SourceFile::Append,
            offset,
            length,
        };

        // There are edge cases if you do an insert
        // at the beginning or end of the file.
        if offset == 0 {
            self.piece_table.insert(0, piece);
            return;
        }
        else if offset + length == self.length {
            self.piece_table.push(piece);
            return;
        }

        // The insertion may create as many as three pieces. It can
        // split a piece that already exists into two parts and then
        // goes in between them.
        let split_index = match self.get_at_offset(offset) {
            Some(v) => v,
            None => return
        };
        let split_piece = self.piece_table.remove(split_index);

        let lower_size  = offset - split_piece.offset;
        let upper_size  = (split_piece.offset + split_piece.length) - offset;

        if upper_size > 0 {
            self.piece_table.insert(split_index, Piece {
                file   : split_piece.file,
                offset : split_piece.offset + lower_size,
                length : upper_size,
            });
        }

        self.piece_table.insert(split_index, Piece {
            file   : SourceFile::Append,
            offset : append_offset,
            length,
        });

        if lower_size > 0 {
            self.piece_table.insert(split_index, Piece {
                file   : split_piece.file,
                offset : split_piece.offset,
                length : lower_size,
            });
        }
    }
}
<commit_msg>feat(PieceFile): add tests for `insert`<commit_after>//! byt - io
//!
//! Implements a piece table to abstract over accessing and
//! modifying a file on disk.

// EXTERNS

// LIBRARY INCLUDES
use std::fmt;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Empty;
use std::io::BufReader;
use std::io::Read;
use std::io;

// SUBMODULES

// LOCAL INCLUDES

#[derive(Debug, Copy, Clone, PartialEq)]
enum SourceFile {
    /// The original file on disk.
    Original,
    /// The append file, held in memory.
    Append
}

/// A Piece stores some information about a part of the
/// modified file.
struct Piece {
    /// The file this piece refers to.
    file : SourceFile,
    /// The offset (in bytes) of the text in this Piece.
    offset : u32,
    /// The length of the text in this Piece.
    length : u32
}

impl fmt::Display for Piece {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({:?}, {}, {})", self.file, self.offset, self.length)
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
enum Operation {
    Insert,
    Delete
}

/// Records a particular modification of the text.
struct Action {
    op     : Operation,
    offset : u32,
    length : u32
}

/// Implements logical operations on a file that are not written
/// until asked to.
pub struct PieceFile {
    /// Stores all mutation operations of the file so that we can
    /// undo.
    actions : Vec<Action>,
    /// The in-memory append string. All edits will refer to bytes
    /// stored here.
    append_file : String,
    /// The total size (in bytes) of the PieceFile.
    length : u32,
    /// The path of the file this PieceFile refers to.
    path : String,
    /// Stores all current Pieces.
    piece_table : Vec<Piece>,
    /// The seekable file reader.
    reader : Option<BufReader<File>>,
}

impl PieceFile {
    // #################################
    // P R I V A T E  F U N C T I O N S
    // #################################
    /// Get a Piece at a particular offset.
    fn get_at_offset(&self, offset : u32) -> Option<usize> {
        let mut logical_length = 0;

        for index in 0..self.piece_table.len() {
            let piece = &self.piece_table[index];
            logical_length += piece.length;

            if offset > logical_length {
                continue;
            }

            return Some(index);
        }
        None
    }

    // ###############################
    // P U B L I C  F U N C T I O N S
    // ###############################
    /// Open a new PieceFile. If the file doesn't exist, it is created.
    pub fn open(path : String) -> io::Result<PieceFile> {
        let file = OpenOptions::new()
            .read(true)
            .open(path.clone()).unwrap();

        // Get the length of the file to initialize the first Piece
        let size = file.metadata().unwrap().len() as u32;

        let mut piece_file = PieceFile {
            actions     : Vec::new(),
            append_file : String::new(),
            length      : size,
            path,
            piece_table : Vec::new(),
            reader      : Some(BufReader::new(file)),
        };

        piece_file.piece_table.push(Piece {
            file : SourceFile::Original,
            offset : 0,
            length : size as u32,
        });

        Ok(piece_file)
    }

    /// Create a new empty PieceFile.
    pub fn empty() -> io::Result<PieceFile> {
        let mut piece_file = PieceFile {
            actions     : Vec::new(),
            append_file : String::new(),
            length      : 0,
            path        : String::from(""),
            piece_table : Vec::new(),
            reader      : None,
        };

        Ok(piece_file)
    }

    /// Insert some text.
    pub fn insert(&mut self, text : &str, offset : u32) {
        let length = text.len() as u32;
        let append_offset = self.append_file.len() as u32;

        self.append_file += text;
        self.length      += length;

        let piece = Piece {
            file   : SourceFile::Append,
            offset : append_offset,
            length,
        };

        let action = Action {
            op : Operation::Insert,
            offset,
            length,
        };

        self.actions.push(action);

        // There are edge cases if you do an insert
        // at the beginning or end of the file.
        if offset == 0 {
            self.piece_table.insert(0, piece);
            return;
        }
        else if offset + length == self.length {
            self.piece_table.push(piece);
            return;
        }

        // The insertion may create as many as three pieces. It can
        // split a piece that already exists into two parts and then
        // goes in between them.
        let split_index = match self.get_at_offset(offset) {
            Some(v) => v,
            None => return
        };
        let split_piece = self.piece_table.remove(split_index);

        let lower_size  = offset - split_piece.offset;
        let upper_size  = (split_piece.offset + split_piece.length) - offset;

        if upper_size > 0 {
            self.piece_table.insert(split_index, Piece {
                file   : split_piece.file,
                offset : split_piece.offset + lower_size,
                length : upper_size,
            });
        }

        self.piece_table.insert(split_index, piece);

        if lower_size > 0 {
            self.piece_table.insert(split_index, Piece {
                file   : split_piece.file,
                offset : split_piece.offset,
                length : lower_size,
            });
        }
    }
}

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

    #[test]
    fn it_inserts() {
        let mut file = PieceFile::empty().unwrap();
        assert_eq!(file.piece_table.len(), 0);

        file.insert("foo", 0);
        file.insert("bar", 0);

        let piece_table = &file.piece_table;
        assert_eq!(piece_table.len(), 2);

        let first_element  = &piece_table[0];
        let second_element = &piece_table[1];

        assert_eq!(first_element.offset, 3);
        assert_eq!(first_element.length, 3);

        assert_eq!(second_element.offset, 0);
        assert_eq!(second_element.length, 3);

        let action = &file.actions[0];
        assert_eq!(action.op, Operation::Insert);
    }
}
<|endoftext|>


<commit_before>#![crate_name = "chmod"]

/*
 * This file is part of the uutils coreutils package.
 *
 * (c) Arcterus <arcterus@mail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

#![feature(macro_rules, phase)]

#![allow(unused_variables)]  // only necessary while the TODOs still exist

extern crate getopts;
extern crate libc;
extern crate native;
extern crate regex;
#[phase(plugin)] extern crate regex_macros;

use std::io::fs;
use std::io::fs::PathExtensions;
use std::num::from_str_radix;
use regex::Regex;

#[path = "../common/util.rs"]
mod util;

const NAME: &'static str = "chmod";
const VERSION: &'static str = "1.0.0";

pub fn uumain(args: Vec<String>) -> int {
    let program = args[0].clone();

    let opts = [
        getopts::optflag("c", "changes", "like verbose but report only when a change is made (unimplemented)"),
        getopts::optflag("f", "quiet", "suppress most error messages (unimplemented)"), // TODO: support --silent
        getopts::optflag("v", "verbose", "output a diagnostic for every file processed (unimplemented)"),
        getopts::optflag("", "no-preserve-root", "do not treat '/' specially (the default)"),
        getopts::optflag("", "preserve-root", "fail to operate recursively on '/'"),
        getopts::optflagopt("", "reference", "use RFILE's mode instead of MODE values", "RFILE"),
        getopts::optflag("R", "recursive", "change files and directories recursively"),
        getopts::optflag("h", "help", "display this help and exit"),
        getopts::optflag("V", "version", "output version information and exit")
    ];
    // TODO: sanitize input for - at beginning (e.g. chmod -x testfile).  Solution is to add a to -x, making a-x
    let mut matches = match getopts::getopts(args.tail(), opts) {
        Ok(m) => m,
        Err(f) => {
            crash!(1, "{}", f)
        }
    };
    if matches.opt_present("help") {
        println!("{name} v{version}

Usage:
  {program} [OPTION]... MODE[,MODE]... FILE...
  {program} [OPTION]... OCTAL-MODE FILE...
  {program} [OPTION]... --reference=RFILE FILE...

{usage}
Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.",
               name = NAME, version = VERSION, program = program,
               usage = getopts::usage("Change the mode of each FILE to MODE. \
                                       With --reference, change the mode of \
                                       each FILE to that of RFILE.", opts));
    } else if matches.opt_present("version") {
        println!("{} v{}", NAME, VERSION);
    } else if matches.free.is_empty() && matches.opt_present("reference") || matches.free.len() < 2 {
        show_error!("missing an argument");
        show_error!("for help, try '{} --help'", program);
        return 1;
    } else {
        let changes = matches.opt_present("changes");
        let quiet = matches.opt_present("quiet");
        let verbose = matches.opt_present("verbose");
        let preserve_root = matches.opt_present("preserve-root");
        let recursive = matches.opt_present("recursive");
        let fmode = matches.opt_str("reference").and_then(|fref| {
            match native::io::file::stat(&fref.to_c_str()) {
                Ok(stat) => Some(stat.perm),
                Err(f) => crash!(1, "{}", f)
            }
        });
        let cmode =
            if fmode.is_none() {
                let mode = matches.free.remove(0).unwrap();  // there are at least two elements
                match verify_mode(mode.as_slice()) {
                    Ok(_) => Some(mode),
                    Err(f) => {
                        show_error!("{}", f);
                        return 1;
                    }
                }
            } else {
                None
            };
        match chmod(matches.free, changes, quiet, verbose, preserve_root,
                    recursive, fmode, cmode.as_ref()) {
            Ok(()) => {}
            Err(e) => return e
        }
    }

    0
}

#[cfg(unix)]
#[inline]
fn verify_mode(modes: &str) -> Result<(), String> {
    static REGEXP: regex::Regex = regex!(r"^[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+$");
    for mode in modes.split(',') {
        if !REGEXP.is_match(mode) {
            return Err(format!("invalid mode '{}'", mode));
        }
    }
    Ok(())
}

#[cfg(windows)]
#[inline]
// XXX: THIS IS NOT TESTED!!!
fn verify_mode(mode: &str) -> Result<(), String> {
    static REGEXP: regex::Regex = regex!(r"^[ugoa]*(?:[-+=](?:([rwxXst]*)|[ugo]))+|[-+=]?([0-7]+)$");
    for mode in modes.split(',') {
        match REGEXP.captures(mode) {
            Some(cap) => {
                let symbols = cap.at(1);
                let numbers = cap.at(2);
                if symbols.contains("s") || symbols.contains("t") {
                    return Err("The 's' and 't' modes are not supported on Windows".to_string());
                } else if numbers.len() >= 4 && numbers.slice_to(num_len - 3).find(|ch| ch != '0').is_some() {
                    return Err("Setuid, setgid, and sticky modes are not supported on Windows".to_string());
                }
            }
            None => return Err(format!("invalid mode '{}'", mode))
        }
    }
    Ok(())
}

fn chmod(files: Vec<String>, changes: bool, quiet: bool, verbose: bool, preserve_root: bool, recursive: bool, fmode: Option<u64>, cmode: Option<&String>) -> Result<(), int> {
    let mut r = Ok(());

    for filename in files.iter() {
        let filename = filename.as_slice();
        let file = Path::new(filename);
        if file.exists() {
            if file.is_dir() {
                if !preserve_root || filename != "/" {
                    if recursive {
                        let walk_dir = match fs::walk_dir(&file) {
                            Ok(m) => m,
                            Err(f) => {
                                crash!(1, "{}", f.to_string());
                            }
                        };
                        r = chmod(walk_dir.map(|x| x.as_str().unwrap().to_string()).collect(), changes, quiet, verbose, preserve_root, recursive, fmode, cmode).and(r);
                        r = chmod_file(&file, filename, changes, quiet, verbose, fmode, cmode).and(r);
                    }
                } else {
                    show_error!("could not change permissions of directory '{}'",
                                filename);
                    r = Err(1);
                }
            } else {
                r = chmod_file(&file, filename.as_slice(), changes, quiet, verbose, fmode, cmode).and(r);
            }
        } else {
            show_error!("no such file or directory '{}'", filename);
            r = Err(1);
        }
    }

    r
}

fn chmod_file(file: &Path, name: &str, changes: bool, quiet: bool, verbose: bool, fmode: Option<u64>, cmode: Option<&String>) -> Result<(), int> {
    let path = name.to_c_str();
    match fmode {
        Some(mode) => {
            match native::io::file::chmod(&path, mode as uint) {
                Ok(_) => { /* TODO: handle changes, quiet, and verbose */ }
                Err(f) => {
                    show_error!("{}", f);
                    return Err(1);
                }
            }
        }
        None => {
            // TODO: make the regex processing occur earlier (i.e. once in the main function)
            static REGEXP: regex::Regex = regex!(r"^(([ugoa]*)((?:[-+=](?:[rwxXst]*|[ugo]))+))|([-+=]?[0-7]+)$");
            let mut fperm = match native::io::file::stat(&path) {
                Ok(stat) => stat.perm,
                Err(f) => {
                    show_error!("{}", f);
                    return Err(1);
                }
            };
            for mode in cmode.unwrap().as_slice().split(',') {  // cmode is guaranteed to be Some in this case
                let cap = REGEXP.captures(mode).unwrap();  // mode was verified earlier, so this is safe
                if cap.at(1) != "" {
                    // symbolic
                    let mut levels = cap.at(2);
                    if levels.len() == 0 {
                        levels = "a";
                    }
                    let change_str = cap.at(3).to_string() + "+";
                    let change = change_str.as_slice();
                    let mut action = change.char_at(0);
                    let mut rwx = 0;
                    let mut special = 0;
                    let mut special_changed = false;
                    for ch in change.slice_from(1).chars() {
                        match ch {
                            '+' | '-' | '=' => {
                                for level in levels.chars() {
                                    let (rwx, mask) = match level {
                                        'u' => (rwx << 6, 0o7077),
                                        'g' => (rwx << 3, 0o7707),
                                        'o' => (rwx, 0o7770),
                                        'a' => ((rwx << 6) | (rwx << 3) | rwx, 0o7000),
                                        _ => unreachable!()
                                    };
                                    match action {
                                        '+' => fperm |= rwx,
                                        '-' => fperm &= !rwx,
                                        '=' => fperm = (fperm & mask) | rwx,
                                        _ => unreachable!()
                                    }
                                }
                                if special_changed {
                                    match action {
                                        '+' => fperm |= special,
                                        '-' => fperm &= !special,
                                        '=' => fperm &= special | 0o0777,
                                        _ => unreachable!()
                                    }
                                }
                                action = ch;
                                rwx = 0;
                                special = 0;
                                special_changed = false;
                            }
                            'r' => rwx |= 0o004,
                            'w' => rwx |= 0o002,
                            'x' => rwx |= 0o001,
                            'X' => {
                                if file.is_dir() || (fperm & 0o0111) != 0 {
                                    rwx |= 0o001;
                                }
                            }
                            's' => {
                                special |= 0o4000 | 0o2000;
                                special_changed = true;
                            }
                            't' => {
                                special |= 0o1000;
                                special_changed = true;
                            }
                            'u' => rwx = (fperm >> 6) & 0o007,
                            'g' => rwx = (fperm >> 3) & 0o007,
                            'o' => rwx = (fperm >> 0) & 0o007,
                            _ => unreachable!()
                        }
                    }
                } else {
                    // numeric
                    let change = cap.at(4);
                    let ch = change.char_at(0);
                    let (action, slice) = match ch {
                        '+' | '-' | '=' => (ch, change.slice_from(1)),
                        _ => ('=', change)
                    };
                    let mode = from_str_radix::<u64>(slice, 8).unwrap();  // already verified
                    match action {
                        '+' => fperm |= mode,
                        '-' => fperm &= !mode,
                        '=' => fperm = mode,
                        _ => unreachable!()
                    }
                }
                match native::io::file::chmod(&path, fperm as uint) {
                    Ok(_) => { /* TODO: see above */ }
                    Err(f) => {
                        show_error!("{}", f);
                        return Err(1);
                    }
                }
            }
        }
    }

    Ok(())
}
<commit_msg>chmod: update for latest Rust<commit_after>#![crate_name = "chmod"]

/*
 * This file is part of the uutils coreutils package.
 *
 * (c) Arcterus <arcterus@mail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

#![feature(macro_rules, phase)]

#![allow(unused_variables)]  // only necessary while the TODOs still exist

extern crate getopts;
extern crate libc;
extern crate native;
extern crate regex;
#[phase(plugin)] extern crate regex_macros;

use std::io::fs;
use std::io::fs::PathExtensions;
use std::io::IoError;
use std::mem;
use std::num::from_str_radix;
use regex::Regex;

#[path = "../common/util.rs"]
mod util;

const NAME: &'static str = "chmod";
const VERSION: &'static str = "1.0.0";

pub fn uumain(args: Vec<String>) -> int {
    let program = args[0].clone();

    let opts = [
        getopts::optflag("c", "changes", "like verbose but report only when a change is made (unimplemented)"),
        getopts::optflag("f", "quiet", "suppress most error messages (unimplemented)"), // TODO: support --silent
        getopts::optflag("v", "verbose", "output a diagnostic for every file processed (unimplemented)"),
        getopts::optflag("", "no-preserve-root", "do not treat '/' specially (the default)"),
        getopts::optflag("", "preserve-root", "fail to operate recursively on '/'"),
        getopts::optflagopt("", "reference", "use RFILE's mode instead of MODE values", "RFILE"),
        getopts::optflag("R", "recursive", "change files and directories recursively"),
        getopts::optflag("h", "help", "display this help and exit"),
        getopts::optflag("V", "version", "output version information and exit")
    ];
    // TODO: sanitize input for - at beginning (e.g. chmod -x testfile).  Solution is to add a to -x, making a-x
    let mut matches = match getopts::getopts(args.tail(), opts) {
        Ok(m) => m,
        Err(f) => {
            crash!(1, "{}", f)
        }
    };
    if matches.opt_present("help") {
        println!("{name} v{version}

Usage:
  {program} [OPTION]... MODE[,MODE]... FILE...
  {program} [OPTION]... OCTAL-MODE FILE...
  {program} [OPTION]... --reference=RFILE FILE...

{usage}
Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.",
               name = NAME, version = VERSION, program = program,
               usage = getopts::usage("Change the mode of each FILE to MODE. \
                                       With --reference, change the mode of \
                                       each FILE to that of RFILE.", opts));
    } else if matches.opt_present("version") {
        println!("{} v{}", NAME, VERSION);
    } else if matches.free.is_empty() && matches.opt_present("reference") || matches.free.len() < 2 {
        show_error!("missing an argument");
        show_error!("for help, try '{} --help'", program);
        return 1;
    } else {
        let changes = matches.opt_present("changes");
        let quiet = matches.opt_present("quiet");
        let verbose = matches.opt_present("verbose");
        let preserve_root = matches.opt_present("preserve-root");
        let recursive = matches.opt_present("recursive");
        let fmode = matches.opt_str("reference").and_then(|fref| {
            let mut stat = unsafe { mem::uninitialized() };
            let statres = unsafe { libc::stat(fref.as_slice().as_ptr() as *const i8, &mut stat as *mut libc::stat) };
            if statres == 0 {
                Some(stat.st_mode)
            } else {
                crash!(1, "{}", IoError::last_error())
            }
        });
        let cmode =
            if fmode.is_none() {
                let mode = matches.free.remove(0).unwrap();  // there are at least two elements
                match verify_mode(mode.as_slice()) {
                    Ok(_) => Some(mode),
                    Err(f) => {
                        show_error!("{}", f);
                        return 1;
                    }
                }
            } else {
                None
            };
        match chmod(matches.free, changes, quiet, verbose, preserve_root,
                    recursive, fmode, cmode.as_ref()) {
            Ok(()) => {}
            Err(e) => return e
        }
    }

    0
}

#[cfg(unix)]
#[inline]
fn verify_mode(modes: &str) -> Result<(), String> {
    static REGEXP: regex::Regex = regex!(r"^[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+$");
    for mode in modes.split(',') {
        if !REGEXP.is_match(mode) {
            return Err(format!("invalid mode '{}'", mode));
        }
    }
    Ok(())
}

#[cfg(windows)]
#[inline]
// XXX: THIS IS NOT TESTED!!!
fn verify_mode(mode: &str) -> Result<(), String> {
    static REGEXP: regex::Regex = regex!(r"^[ugoa]*(?:[-+=](?:([rwxXst]*)|[ugo]))+|[-+=]?([0-7]+)$");
    for mode in modes.split(',') {
        match REGEXP.captures(mode) {
            Some(cap) => {
                let symbols = cap.at(1);
                let numbers = cap.at(2);
                if symbols.contains("s") || symbols.contains("t") {
                    return Err("The 's' and 't' modes are not supported on Windows".to_string());
                } else if numbers.len() >= 4 && numbers.slice_to(num_len - 3).find(|ch| ch != '0').is_some() {
                    return Err("Setuid, setgid, and sticky modes are not supported on Windows".to_string());
                }
            }
            None => return Err(format!("invalid mode '{}'", mode))
        }
    }
    Ok(())
}

fn chmod(files: Vec<String>, changes: bool, quiet: bool, verbose: bool, preserve_root: bool, recursive: bool, fmode: Option<u32>, cmode: Option<&String>) -> Result<(), int> {
    let mut r = Ok(());

    for filename in files.iter() {
        let filename = filename.as_slice();
        let file = Path::new(filename);
        if file.exists() {
            if file.is_dir() {
                if !preserve_root || filename != "/" {
                    if recursive {
                        let walk_dir = match fs::walk_dir(&file) {
                            Ok(m) => m,
                            Err(f) => {
                                crash!(1, "{}", f.to_string());
                            }
                        };
                        r = chmod(walk_dir.map(|x| x.as_str().unwrap().to_string()).collect(), changes, quiet, verbose, preserve_root, recursive, fmode, cmode).and(r);
                        r = chmod_file(&file, filename, changes, quiet, verbose, fmode, cmode).and(r);
                    }
                } else {
                    show_error!("could not change permissions of directory '{}'",
                                filename);
                    r = Err(1);
                }
            } else {
                r = chmod_file(&file, filename.as_slice(), changes, quiet, verbose, fmode, cmode).and(r);
            }
        } else {
            show_error!("no such file or directory '{}'", filename);
            r = Err(1);
        }
    }

    r
}

fn chmod_file(file: &Path, name: &str, changes: bool, quiet: bool, verbose: bool, fmode: Option<u32>, cmode: Option<&String>) -> Result<(), int> {
    let path = name.to_c_str();
    match fmode {
        Some(mode) => {
            if unsafe { libc::chmod(path.as_ptr(), mode) } == 0 {
                // TODO: handle changes, quiet, and verbose
            } else {
                show_error!("{}", IoError::last_error());
                return Err(1);
            }
        }
        None => {
            // TODO: make the regex processing occur earlier (i.e. once in the main function)
            static REGEXP: regex::Regex = regex!(r"^(([ugoa]*)((?:[-+=](?:[rwxXst]*|[ugo]))+))|([-+=]?[0-7]+)$");
            let mut stat = unsafe { mem::uninitialized() };
            let statres = unsafe { libc::stat(path.as_ptr(), &mut stat as *mut libc::stat) };
            let mut fperm =
                if statres == 0 {
                    stat.st_mode
                } else {
                    show_error!("{}", IoError::last_error());
                    return Err(1);
                };
            for mode in cmode.unwrap().as_slice().split(',') {  // cmode is guaranteed to be Some in this case
                let cap = REGEXP.captures(mode).unwrap();  // mode was verified earlier, so this is safe
                if cap.at(1) != "" {
                    // symbolic
                    let mut levels = cap.at(2);
                    if levels.len() == 0 {
                        levels = "a";
                    }
                    let change_str = cap.at(3).to_string() + "+";
                    let change = change_str.as_slice();
                    let mut action = change.char_at(0);
                    let mut rwx = 0;
                    let mut special = 0;
                    let mut special_changed = false;
                    for ch in change.slice_from(1).chars() {
                        match ch {
                            '+' | '-' | '=' => {
                                for level in levels.chars() {
                                    let (rwx, mask) = match level {
                                        'u' => (rwx << 6, 0o7077),
                                        'g' => (rwx << 3, 0o7707),
                                        'o' => (rwx, 0o7770),
                                        'a' => ((rwx << 6) | (rwx << 3) | rwx, 0o7000),
                                        _ => unreachable!()
                                    };
                                    match action {
                                        '+' => fperm |= rwx,
                                        '-' => fperm &= !rwx,
                                        '=' => fperm = (fperm & mask) | rwx,
                                        _ => unreachable!()
                                    }
                                }
                                if special_changed {
                                    match action {
                                        '+' => fperm |= special,
                                        '-' => fperm &= !special,
                                        '=' => fperm &= special | 0o0777,
                                        _ => unreachable!()
                                    }
                                }
                                action = ch;
                                rwx = 0;
                                special = 0;
                                special_changed = false;
                            }
                            'r' => rwx |= 0o004,
                            'w' => rwx |= 0o002,
                            'x' => rwx |= 0o001,
                            'X' => {
                                if file.is_dir() || (fperm & 0o0111) != 0 {
                                    rwx |= 0o001;
                                }
                            }
                            's' => {
                                special |= 0o4000 | 0o2000;
                                special_changed = true;
                            }
                            't' => {
                                special |= 0o1000;
                                special_changed = true;
                            }
                            'u' => rwx = (fperm >> 6) & 0o007,
                            'g' => rwx = (fperm >> 3) & 0o007,
                            'o' => rwx = (fperm >> 0) & 0o007,
                            _ => unreachable!()
                        }
                    }
                } else {
                    // numeric
                    let change = cap.at(4);
                    let ch = change.char_at(0);
                    let (action, slice) = match ch {
                        '+' | '-' | '=' => (ch, change.slice_from(1)),
                        _ => ('=', change)
                    };
                    let mode = from_str_radix::<u32>(slice, 8).unwrap();  // already verified
                    match action {
                        '+' => fperm |= mode,
                        '-' => fperm &= !mode,
                        '=' => fperm = mode,
                        _ => unreachable!()
                    }
                }
                if unsafe { libc::chmod(path.as_ptr(), fperm) } == 0 {
                    // TODO: see above
                } else {
                    show_error!("{}", IoError::last_error());
                    return Err(1);
                }
            }
        }
    }

    Ok(())
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

extern crate gleam;
extern crate glutin;
extern crate webrender;

#[path="common/boilerplate.rs"]
mod boilerplate;

use boilerplate::{Example, HandyDandyRectBuilder};
use webrender::api::*;

struct App {
    cursor_position: WorldPoint,
}

impl Example for App {
    fn render(&mut self,
              _api: &RenderApi,
              builder: &mut DisplayListBuilder,
              _resources: &mut ResourceUpdates,
              layout_size: LayoutSize,
              _pipeline_id: PipelineId,
              _document_id: DocumentId) {
        let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
        builder.push_stacking_context(ScrollPolicy::Scrollable,
                                      bounds,
                                      None,
                                      TransformStyle::Flat,
                                      None,
                                      MixBlendMode::Normal,
                                      Vec::new());

        if true {   // scrolling and clips stuff
            // let's make a scrollbox
            let scrollbox = (0, 0).to(300, 400);
            builder.push_stacking_context(ScrollPolicy::Scrollable,
                                          LayoutRect::new(LayoutPoint::new(10.0, 10.0),
                                                          LayoutSize::zero()),
                                          None,
                                          TransformStyle::Flat,
                                          None,
                                          MixBlendMode::Normal,
                                          Vec::new());
            // set the scrolling clip
            let clip_id = builder.define_scroll_frame(None,
                                                      (0, 0).by(1000, 1000),
                                                      scrollbox,
                                                      vec![],
                                                      None,
                                                      ScrollSensitivity::ScriptAndInputEvents);
            builder.push_clip_id(clip_id);

            // now put some content into it.
            // start with a white background
            builder.push_rect((0, 0).to(1000, 1000), None, ColorF::new(1.0, 1.0, 1.0, 1.0));

            // let's make a 50x50 blue square as a visual reference
            builder.push_rect((0, 0).to(50, 50), None, ColorF::new(0.0, 0.0, 1.0, 1.0));

            // and a 50x50 green square next to it with an offset clip
            // to see what that looks like
            builder.push_rect((50, 0).to(100, 50),
                              Some(LocalClip::from((60, 10).to(110, 60))),
                              ColorF::new(0.0, 1.0, 0.0, 1.0));

            // Below the above rectangles, set up a nested scrollbox. It's still in
            // the same stacking context, so note that the rects passed in need to
            // be relative to the stacking context.
            let nested_clip_id = builder.define_scroll_frame(None,
                                                             (0, 100).to(300, 400),
                                                             (0, 100).to(200, 300),
                                                             vec![],
                                                             None,
                                                             ScrollSensitivity::ScriptAndInputEvents);
            builder.push_clip_id(nested_clip_id);

            // give it a giant gray background just to distinguish it and to easily
            // visually identify the nested scrollbox
            builder.push_rect((-1000, -1000).to(5000, 5000), None, ColorF::new(0.5, 0.5, 0.5, 1.0));

            // add a teal square to visualize the scrolling/clipping behaviour
            // as you scroll the nested scrollbox with WASD keys
            builder.push_rect((0, 100).to(50, 150), None, ColorF::new(0.0, 1.0, 1.0, 1.0));

            // just for good measure add another teal square in the bottom-right
            // corner of the nested scrollframe content, which can be scrolled into
            // view by the user
            builder.push_rect((250, 350).to(300, 400), None, ColorF::new(0.0, 1.0, 1.0, 1.0));

            builder.pop_clip_id(); // nested_clip_id

            builder.pop_clip_id(); // clip_id
            builder.pop_stacking_context();
        }

        builder.pop_stacking_context();
    }

    fn on_event(&mut self,
                event: glutin::Event,
                api: &RenderApi,
                document_id: DocumentId) -> bool {
        match event {
            glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
                let offset = match key {
                     glutin::VirtualKeyCode::Down => (0.0, -10.0),
                     glutin::VirtualKeyCode::Up => (0.0, 10.0),
                     glutin::VirtualKeyCode::Right => (-10.0, 0.0),
                     glutin::VirtualKeyCode::Left => (10.0, 0.0),
                     _ => return false,
                };

                api.scroll(document_id,
                           ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
                           self.cursor_position,
                           ScrollEventPhase::Start);
            }
            glutin::Event::MouseMoved(x, y) => {
                self.cursor_position = WorldPoint::new(x as f32, y as f32);
            }
            glutin::Event::MouseWheel(delta, _, event_cursor_position) => {
                if let Some((x, y)) = event_cursor_position {
                    self.cursor_position = WorldPoint::new(x as f32, y as f32);
                }

                const LINE_HEIGHT: f32 = 38.0;
                let (dx, dy) = match delta {
                    glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
                    glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
                };

                api.scroll(document_id,
                           ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
                           self.cursor_position,
                           ScrollEventPhase::Start);
            }
            _ => ()
        }

        false
    }
}

fn main() {
    let mut app = App {
        cursor_position: WorldPoint::zero(),
    };
    boilerplate::main_wrapper(&mut app, None);
}
<commit_msg>Add a sticky frame to the scrolling example<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

extern crate gleam;
extern crate glutin;
extern crate webrender;

#[path="common/boilerplate.rs"]
mod boilerplate;

use boilerplate::{Example, HandyDandyRectBuilder};
use webrender::api::*;

struct App {
    cursor_position: WorldPoint,
}

impl Example for App {
    fn render(&mut self,
              _api: &RenderApi,
              builder: &mut DisplayListBuilder,
              _resources: &mut ResourceUpdates,
              layout_size: LayoutSize,
              _pipeline_id: PipelineId,
              _document_id: DocumentId) {
        let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
        builder.push_stacking_context(ScrollPolicy::Scrollable,
                                      bounds,
                                      None,
                                      TransformStyle::Flat,
                                      None,
                                      MixBlendMode::Normal,
                                      Vec::new());

        if true {   // scrolling and clips stuff
            // let's make a scrollbox
            let scrollbox = (0, 0).to(300, 400);
            builder.push_stacking_context(ScrollPolicy::Scrollable,
                                          LayoutRect::new(LayoutPoint::new(10.0, 10.0),
                                                          LayoutSize::zero()),
                                          None,
                                          TransformStyle::Flat,
                                          None,
                                          MixBlendMode::Normal,
                                          Vec::new());
            // set the scrolling clip
            let clip_id = builder.define_scroll_frame(None,
                                                      (0, 0).by(1000, 1000),
                                                      scrollbox,
                                                      vec![],
                                                      None,
                                                      ScrollSensitivity::ScriptAndInputEvents);
            builder.push_clip_id(clip_id);

            // now put some content into it.
            // start with a white background
            builder.push_rect((0, 0).to(1000, 1000), None, ColorF::new(1.0, 1.0, 1.0, 1.0));

            // let's make a 50x50 blue square as a visual reference
            builder.push_rect((0, 0).to(50, 50), None, ColorF::new(0.0, 0.0, 1.0, 1.0));

            // and a 50x50 green square next to it with an offset clip
            // to see what that looks like
            builder.push_rect((50, 0).to(100, 50),
                              Some(LocalClip::from((60, 10).to(110, 60))),
                              ColorF::new(0.0, 1.0, 0.0, 1.0));

            // Below the above rectangles, set up a nested scrollbox. It's still in
            // the same stacking context, so note that the rects passed in need to
            // be relative to the stacking context.
            let nested_clip_id = builder.define_scroll_frame(None,
                                                             (0, 100).to(300, 400),
                                                             (0, 100).to(200, 300),
                                                             vec![],
                                                             None,
                                                             ScrollSensitivity::ScriptAndInputEvents);
            builder.push_clip_id(nested_clip_id);

            // give it a giant gray background just to distinguish it and to easily
            // visually identify the nested scrollbox
            builder.push_rect((-1000, -1000).to(5000, 5000), None, ColorF::new(0.5, 0.5, 0.5, 1.0));

            // add a teal square to visualize the scrolling/clipping behaviour
            // as you scroll the nested scrollbox
            builder.push_rect((0, 200).to(50, 250), None, ColorF::new(0.0, 1.0, 1.0, 1.0));

            // Add a sticky frame. It will "stick" at a margin of 10px from the top, until
            // the scrollframe scrolls another 60px, at which point it will "unstick". This lines
            // it up with the above teal square as it scrolls out of the visible area of the
            // scrollframe
            let sticky_id = builder.define_sticky_frame(
                None,
                (50, 140).to(100, 190),
                StickyFrameInfo::new(Some(StickySideConstraint{ margin: 10.0, max_offset: 60.0 }),
                                     None, None, None));
            builder.push_clip_id(sticky_id);
            builder.push_rect((50, 140).to(100, 190), None, ColorF::new(0.5, 0.5, 1.0, 1.0));
            builder.pop_clip_id(); // sticky_id

            // just for good measure add another teal square in the bottom-right
            // corner of the nested scrollframe content, which can be scrolled into
            // view by the user
            builder.push_rect((250, 350).to(300, 400), None, ColorF::new(0.0, 1.0, 1.0, 1.0));

            builder.pop_clip_id(); // nested_clip_id

            builder.pop_clip_id(); // clip_id
            builder.pop_stacking_context();
        }

        builder.pop_stacking_context();
    }

    fn on_event(&mut self,
                event: glutin::Event,
                api: &RenderApi,
                document_id: DocumentId) -> bool {
        match event {
            glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
                let offset = match key {
                     glutin::VirtualKeyCode::Down => (0.0, -10.0),
                     glutin::VirtualKeyCode::Up => (0.0, 10.0),
                     glutin::VirtualKeyCode::Right => (-10.0, 0.0),
                     glutin::VirtualKeyCode::Left => (10.0, 0.0),
                     _ => return false,
                };

                api.scroll(document_id,
                           ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
                           self.cursor_position,
                           ScrollEventPhase::Start);
            }
            glutin::Event::MouseMoved(x, y) => {
                self.cursor_position = WorldPoint::new(x as f32, y as f32);
            }
            glutin::Event::MouseWheel(delta, _, event_cursor_position) => {
                if let Some((x, y)) = event_cursor_position {
                    self.cursor_position = WorldPoint::new(x as f32, y as f32);
                }

                const LINE_HEIGHT: f32 = 38.0;
                let (dx, dy) = match delta {
                    glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
                    glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
                };

                api.scroll(document_id,
                           ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
                           self.cursor_position,
                           ScrollEventPhase::Start);
            }
            _ => ()
        }

        false
    }
}

fn main() {
    let mut app = App {
        cursor_position: WorldPoint::zero(),
    };
    boilerplate::main_wrapper(&mut app, None);
}
<|endoftext|>


<commit_before>use ::std;
use ::for_other_imageflow_crates::preludes::external_without_std::*;
use ::ffi;
use ::{Context, CError,  Result, JsonResponse};
use ::ffi::CodecInstance;
use ::ffi::BitmapBgra;
use ::imageflow_types::collections::AddRemoveSet;
use io::IoProxy;
use uuid::Uuid;
use imageflow_types::IoDirection;
use super::*;
use ::std::any::Any;
mod disposal;
mod subimage;
mod screen;
mod bgra;
use self::bgra::BGRA8;
use self::screen::Screen;
use ::gif::Frame;
use ::gif::SetParameter;

pub struct GifDecoder{
    reader: ::gif::Reader<IoProxy>,
    screen: Screen,
    buffer: Option<Vec<u8>>,
    last_frame: Option<Frame<'static>>,
    next_frame: Option<Frame<'static>>
}

impl GifDecoder {
    pub fn create(c: &Context, io: IoProxy, io_id: i32) -> Result<GifDecoder> {

        let mut decoder = ::gif::Decoder::new(io);

        // Important:
        decoder.set(::gif::ColorOutput::Indexed);

        let reader = decoder.read_info().map_err(|e| FlowError::from(e).at(here!()))?;

        let screen = Screen::new(&reader);

        Ok(GifDecoder{
            reader,
            screen,
            buffer: None,
            last_frame: None,
            next_frame: None
        })
    }

    fn read_next_frame_info(&mut self) -> Result<()>{
        self.last_frame = self.next_frame.take();
        // Currently clones local palette
        self.next_frame = self.reader.next_frame_info().map_err(|e| FlowError::from(e).at(here!()))?.map(|v|v.clone());
        Ok(())
    }

    fn create_bitmap_from_screen(&self, c: &Context) -> Result<*mut BitmapBgra>{
        // Create output bitmap and copy to it
        unsafe {
            let w = self.screen.width;
            let h = self.screen.height;
            let copy = ffi::flow_bitmap_bgra_create(c.flow_c(), w as i32, h as i32, false, ffi::PixelFormat::Bgra32);
            if copy == ptr::null_mut() {
                cerror!(c).panic();
            }
            let copy_mut = &mut *copy;

            for row in 0..h{
                let to_row: &mut [BGRA8] = std::slice::from_raw_parts_mut(copy_mut.pixels.offset(copy_mut.stride as isize * row as isize) as *mut BGRA8, w as usize);
                to_row.copy_from_slice(&self.screen.pixels[row * w..(row + 1) * w]);
            }
            Ok(copy)
        }
    }
    pub fn current_frame(&self) -> Option<&Frame>{
        self.last_frame.as_ref()
    }

    pub fn get_repeat(&self) -> Option<::gif::Repeat>{
        // TODO: Fix hack - gif crate doesn't allow reading this
        Some(::gif::Repeat::Infinite)
    }
}

impl Decoder for GifDecoder {
    fn initialize(&mut self, c: &Context) -> Result<()> {
        Ok(())
    }

    fn get_image_info(&mut self, c: &Context) -> Result<s::ImageInfo> {
        Ok(s::ImageInfo {
            frame_decodes_into: s::PixelFormat::Bgra32,
            image_width: self.reader.width() as i32,
            image_height: self.reader.height() as i32,
//            current_frame_index: 0,
//            frame_count: 1,
            // We would have to read in the entire GIF to know!
            preferred_mime_type: "image/gif".to_owned(),
            preferred_extension: "gif".to_owned()
        })
    }

    fn get_exif_rotation_flag(&mut self, c: &Context) -> Result<Option<i32>> {
        Ok(None)
    }

    fn tell_decoder(&mut self, c: &Context, tell: s::DecoderCommand) -> Result<()> {
        Ok(())
    }

    fn read_frame(&mut self, c: &Context) -> Result<*mut BitmapBgra> {
        // Ensure next_frame is present (only called for first frame)
        if self.next_frame.is_none() {
            self.read_next_frame_info().map_err(|e| e.at(here!()))?;
        }

        {
            // Grab a reference
            let frame = self.next_frame.as_ref().ok_or_else(|| nerror!(ErrorKind::InvalidOperation, "read_frame was called without a frame available"))?;

            //Prepare our resuable buffer
            let buf_size = self.reader.width() as usize * self.reader.height() as usize;

            let buf_mut = self.buffer.get_or_insert_with(|| vec![0; buf_size]);
            let mut slice = &mut buf_mut[..self.reader.buffer_size()];

            unsafe {
                ptr::write_bytes(slice.as_mut_ptr(), 0, slice.len() - 1);
            }
            //Read into that buffer
            self.reader.read_into_buffer(slice).map_err(|e| FlowError::from(e).at(here!()))?;

            // Render / apply disposal
            //TODO: allocs: Disposal currently allocates a new copy every blit (for previous frame)
            self.screen.blit(frame, slice).map_err(|e| nerror!(ErrorKind::GifDecodingError, "{:?}", e))?; //Missing palette?
        }
        // Try to read the next frame;
        self.read_next_frame_info().map_err(|e| e.at(here!()))?;

        self.create_bitmap_from_screen(c)
    }
    fn has_more_frames(&mut self) -> Result<bool> {
        Ok(self.next_frame.is_some())
    }
    fn as_any(&self) -> &Any {
        self as &Any
    }
}

pub struct GifEncoder{
    io_id: i32,
    encoder: ::gif::Encoder<IoProxy>,
    io_ref: &'static IoProxy, //unsafe self-referential,
    frame_ix: i32
}

impl GifEncoder{
    pub(crate) fn create(c: &Context, preset: &s::EncoderPreset, io: IoProxy, first_frame: &BitmapBgra) -> Result<GifEncoder>{
        Ok(GifEncoder{
            io_id: io.io_id(),
            io_ref: unsafe { &*(&io as *const IoProxy) },
            // Global color table??
            encoder: ::gif::Encoder::new(io, first_frame.w as u16, first_frame.h as u16, &[]).map_err(|e| FlowError::from_gif_encoder(e).at(here!()))?,
            frame_ix: 0
        })
    }
}

impl Encoder for GifEncoder{
    fn write_frame(&mut self, c: &Context, preset: &s::EncoderPreset, frame: &mut BitmapBgra, decoder_io_ids: &[i32]) -> Result<s::EncodeResult> {

        let mut decoded_frame = None;
        let mut repeat = None;
        for io_id in decoder_io_ids{

            let mut codec = c.get_codec(*io_id).map_err(|e| e.at(here!()))?;
            let gif_decoder = codec.get_decoder().map_err(|e| e.at(here!()))?.as_any().downcast_ref::<GifDecoder>();

            if let Some(d) = gif_decoder {

                repeat = d.get_repeat();
                decoded_frame = d.last_frame.clone(); //TODO: clones local palette; expensive, not used
                break;
            }
        }

//        eprintln!("decoders: {:?}, found_frame: {}", decoder_io_ids, decoded_frame.is_some() );

        unsafe {
            let mut pixels = Vec::new();
            pixels.extend_from_slice(frame.pixels_slice_mut().expect("Frame must have pixel buffer"));

            let mut f = match frame.fmt {
                ::ffi::PixelFormat::Bgr24 => Ok(from_bgr_with_stride(frame.w as u16, frame.h as u16, &mut pixels, frame.stride as usize)),
                ::ffi::PixelFormat::Bgra32 => Ok(from_bgra_with_stride(frame.w as u16, frame.h as u16, &mut pixels, frame.stride as usize)),
                ::ffi::PixelFormat::Bgr32 => Ok(from_bgrx_with_stride(frame.w as u16, frame.h as u16, &mut pixels, frame.stride as usize)),
                other =>  Err(nerror!(ErrorKind::InvalidArgument, "PixelFormat {:?} not supported for gif encoding", frame.fmt))
            }?;

            if let Some(from) = decoded_frame{
                f.delay = from.delay;
                f.needs_user_input = from.needs_user_input;
            }
            if self.frame_ix == 0 {
                // Only write before any frames
                if let Some(r) = repeat {
                    eprintln!("Writing repeat");
                    self.encoder.write_extension(::gif::ExtensionData::Repetitions(r)).map_err(|e| FlowError::from_gif_encoder(e).at(here!()))?;
                }else{
                    eprintln!("Skipping repeat");
                }
            }

            // TODO: Overhaul encoding
            // delay
            // dispose method
            // rect
            // transparency??

            self.encoder.write_frame(&f).map_err(|e| FlowError::from_gif_encoder(e).at(here!()))?;

            self.frame_ix+=1;
            Ok(
                s::EncodeResult{
                    w: frame.w as i32,
                    h: frame.h as i32,
                    io_id: self.io_id,
                    bytes: ::imageflow_types::ResultBytes::Elsewhere,
                    preferred_extension: "gif".to_owned(),
                    preferred_mime_type: "image/gif".to_owned()
                }
            )
        }
    }
    fn get_io(&self) -> Result<&IoProxy> {
        Ok(self.io_ref)
    }
}

fn remove_padding(width: u16, pixels: &[u8], stride: usize) -> Vec<u8>{
    pixels.chunks(stride).flat_map(|s| s[0..width as usize * 4].iter().map(|v| *v)).collect()
}
/// Creates a frame from pixels in RGBA format.
///
/// *Note: This method is not optimized for speed.*
pub fn from_bgra_with_stride(width: u16, height: u16, pixels: &mut [u8], stride: usize) -> ::gif::Frame<'static> {
    let mut without_padding = remove_padding(width, pixels, stride);
    for pix in without_padding.chunks_mut(4) {
        let a = pix[0];
        pix[0] = pix[2];
        pix[2] = a;
    }
    ::gif::Frame::from_rgba(width, height, &mut without_padding)
}

pub fn from_bgrx_with_stride(width: u16, height: u16, pixels: &mut [u8], stride: usize) -> ::gif::Frame<'static> {
    let mut without_padding = remove_padding(width, pixels, stride);

    for pix in without_padding.chunks_mut(4) {
        let a = pix[0];
        pix[0] = pix[2];
        pix[2] = a;
        pix[3] = 0xFF;
    }
    ::gif::Frame::from_rgba(width, height, &mut without_padding)
}

/// Creates a frame from pixels in RGB format.
///
/// *Note: This method is not optimized for speed.*
pub fn from_bgr_with_stride(width: u16, height: u16, pixels: &[u8], stride: usize) -> ::gif::Frame<'static> {
    let mut without_padding = remove_padding(width, pixels, stride);
    for pix in without_padding.chunks_mut(3) {
        let a = pix[0];
        pix[0] = pix[2];
        pix[2] = a;
    }
    ::gif::Frame::from_rgb(width, height, &mut without_padding)
}
<commit_msg>Remove eprintln from gif encoder<commit_after>use ::std;
use ::for_other_imageflow_crates::preludes::external_without_std::*;
use ::ffi;
use ::{Context, CError,  Result, JsonResponse};
use ::ffi::CodecInstance;
use ::ffi::BitmapBgra;
use ::imageflow_types::collections::AddRemoveSet;
use io::IoProxy;
use uuid::Uuid;
use imageflow_types::IoDirection;
use super::*;
use ::std::any::Any;
mod disposal;
mod subimage;
mod screen;
mod bgra;
use self::bgra::BGRA8;
use self::screen::Screen;
use ::gif::Frame;
use ::gif::SetParameter;

pub struct GifDecoder{
    reader: ::gif::Reader<IoProxy>,
    screen: Screen,
    buffer: Option<Vec<u8>>,
    last_frame: Option<Frame<'static>>,
    next_frame: Option<Frame<'static>>
}

impl GifDecoder {
    pub fn create(c: &Context, io: IoProxy, io_id: i32) -> Result<GifDecoder> {

        let mut decoder = ::gif::Decoder::new(io);

        // Important:
        decoder.set(::gif::ColorOutput::Indexed);

        let reader = decoder.read_info().map_err(|e| FlowError::from(e).at(here!()))?;

        let screen = Screen::new(&reader);

        Ok(GifDecoder{
            reader,
            screen,
            buffer: None,
            last_frame: None,
            next_frame: None
        })
    }

    fn read_next_frame_info(&mut self) -> Result<()>{
        self.last_frame = self.next_frame.take();
        // Currently clones local palette
        self.next_frame = self.reader.next_frame_info().map_err(|e| FlowError::from(e).at(here!()))?.map(|v|v.clone());
        Ok(())
    }

    fn create_bitmap_from_screen(&self, c: &Context) -> Result<*mut BitmapBgra>{
        // Create output bitmap and copy to it
        unsafe {
            let w = self.screen.width;
            let h = self.screen.height;
            let copy = ffi::flow_bitmap_bgra_create(c.flow_c(), w as i32, h as i32, false, ffi::PixelFormat::Bgra32);
            if copy == ptr::null_mut() {
                cerror!(c).panic();
            }
            let copy_mut = &mut *copy;

            for row in 0..h{
                let to_row: &mut [BGRA8] = std::slice::from_raw_parts_mut(copy_mut.pixels.offset(copy_mut.stride as isize * row as isize) as *mut BGRA8, w as usize);
                to_row.copy_from_slice(&self.screen.pixels[row * w..(row + 1) * w]);
            }
            Ok(copy)
        }
    }
    pub fn current_frame(&self) -> Option<&Frame>{
        self.last_frame.as_ref()
    }

    pub fn get_repeat(&self) -> Option<::gif::Repeat>{
        // TODO: Fix hack - gif crate doesn't allow reading this
        Some(::gif::Repeat::Infinite)
    }
}

impl Decoder for GifDecoder {
    fn initialize(&mut self, c: &Context) -> Result<()> {
        Ok(())
    }

    fn get_image_info(&mut self, c: &Context) -> Result<s::ImageInfo> {
        Ok(s::ImageInfo {
            frame_decodes_into: s::PixelFormat::Bgra32,
            image_width: self.reader.width() as i32,
            image_height: self.reader.height() as i32,
//            current_frame_index: 0,
//            frame_count: 1,
            // We would have to read in the entire GIF to know!
            preferred_mime_type: "image/gif".to_owned(),
            preferred_extension: "gif".to_owned()
        })
    }

    fn get_exif_rotation_flag(&mut self, c: &Context) -> Result<Option<i32>> {
        Ok(None)
    }

    fn tell_decoder(&mut self, c: &Context, tell: s::DecoderCommand) -> Result<()> {
        Ok(())
    }

    fn read_frame(&mut self, c: &Context) -> Result<*mut BitmapBgra> {
        // Ensure next_frame is present (only called for first frame)
        if self.next_frame.is_none() {
            self.read_next_frame_info().map_err(|e| e.at(here!()))?;
        }

        {
            // Grab a reference
            let frame = self.next_frame.as_ref().ok_or_else(|| nerror!(ErrorKind::InvalidOperation, "read_frame was called without a frame available"))?;

            //Prepare our resuable buffer
            let buf_size = self.reader.width() as usize * self.reader.height() as usize;

            let buf_mut = self.buffer.get_or_insert_with(|| vec![0; buf_size]);
            let mut slice = &mut buf_mut[..self.reader.buffer_size()];

            unsafe {
                ptr::write_bytes(slice.as_mut_ptr(), 0, slice.len() - 1);
            }
            //Read into that buffer
            self.reader.read_into_buffer(slice).map_err(|e| FlowError::from(e).at(here!()))?;

            // Render / apply disposal
            //TODO: allocs: Disposal currently allocates a new copy every blit (for previous frame)
            self.screen.blit(frame, slice).map_err(|e| nerror!(ErrorKind::GifDecodingError, "{:?}", e))?; //Missing palette?
        }
        // Try to read the next frame;
        self.read_next_frame_info().map_err(|e| e.at(here!()))?;

        self.create_bitmap_from_screen(c)
    }
    fn has_more_frames(&mut self) -> Result<bool> {
        Ok(self.next_frame.is_some())
    }
    fn as_any(&self) -> &Any {
        self as &Any
    }
}

pub struct GifEncoder{
    io_id: i32,
    encoder: ::gif::Encoder<IoProxy>,
    io_ref: &'static IoProxy, //unsafe self-referential,
    frame_ix: i32
}

impl GifEncoder{
    pub(crate) fn create(c: &Context, preset: &s::EncoderPreset, io: IoProxy, first_frame: &BitmapBgra) -> Result<GifEncoder>{
        Ok(GifEncoder{
            io_id: io.io_id(),
            io_ref: unsafe { &*(&io as *const IoProxy) },
            // Global color table??
            encoder: ::gif::Encoder::new(io, first_frame.w as u16, first_frame.h as u16, &[]).map_err(|e| FlowError::from_gif_encoder(e).at(here!()))?,
            frame_ix: 0
        })
    }
}

impl Encoder for GifEncoder{
    fn write_frame(&mut self, c: &Context, preset: &s::EncoderPreset, frame: &mut BitmapBgra, decoder_io_ids: &[i32]) -> Result<s::EncodeResult> {

        let mut decoded_frame = None;
        let mut repeat = None;
        for io_id in decoder_io_ids{

            let mut codec = c.get_codec(*io_id).map_err(|e| e.at(here!()))?;
            let gif_decoder = codec.get_decoder().map_err(|e| e.at(here!()))?.as_any().downcast_ref::<GifDecoder>();

            if let Some(d) = gif_decoder {

                repeat = d.get_repeat();
                decoded_frame = d.last_frame.clone(); //TODO: clones local palette; expensive, not used
                break;
            }
        }

//        eprintln!("decoders: {:?}, found_frame: {}", decoder_io_ids, decoded_frame.is_some() );

        unsafe {
            let mut pixels = Vec::new();
            pixels.extend_from_slice(frame.pixels_slice_mut().expect("Frame must have pixel buffer"));

            let mut f = match frame.fmt {
                ::ffi::PixelFormat::Bgr24 => Ok(from_bgr_with_stride(frame.w as u16, frame.h as u16, &mut pixels, frame.stride as usize)),
                ::ffi::PixelFormat::Bgra32 => Ok(from_bgra_with_stride(frame.w as u16, frame.h as u16, &mut pixels, frame.stride as usize)),
                ::ffi::PixelFormat::Bgr32 => Ok(from_bgrx_with_stride(frame.w as u16, frame.h as u16, &mut pixels, frame.stride as usize)),
                other =>  Err(nerror!(ErrorKind::InvalidArgument, "PixelFormat {:?} not supported for gif encoding", frame.fmt))
            }?;

            if let Some(from) = decoded_frame{
                f.delay = from.delay;
                f.needs_user_input = from.needs_user_input;
            }
            if self.frame_ix == 0 {
                // Only write before any frames
                if let Some(r) = repeat {
//                    eprintln!("Writing repeat");
                    self.encoder.write_extension(::gif::ExtensionData::Repetitions(r)).map_err(|e| FlowError::from_gif_encoder(e).at(here!()))?;
                }else{
//                    eprintln!("Skipping repeat");
                }
            }

            // TODO: Overhaul encoding
            // delay
            // dispose method
            // rect
            // transparency??

            self.encoder.write_frame(&f).map_err(|e| FlowError::from_gif_encoder(e).at(here!()))?;

            self.frame_ix+=1;
            Ok(
                s::EncodeResult{
                    w: frame.w as i32,
                    h: frame.h as i32,
                    io_id: self.io_id,
                    bytes: ::imageflow_types::ResultBytes::Elsewhere,
                    preferred_extension: "gif".to_owned(),
                    preferred_mime_type: "image/gif".to_owned()
                }
            )
        }
    }
    fn get_io(&self) -> Result<&IoProxy> {
        Ok(self.io_ref)
    }
}

fn remove_padding(width: u16, pixels: &[u8], stride: usize) -> Vec<u8>{
    pixels.chunks(stride).flat_map(|s| s[0..width as usize * 4].iter().map(|v| *v)).collect()
}
/// Creates a frame from pixels in RGBA format.
///
/// *Note: This method is not optimized for speed.*
pub fn from_bgra_with_stride(width: u16, height: u16, pixels: &mut [u8], stride: usize) -> ::gif::Frame<'static> {
    let mut without_padding = remove_padding(width, pixels, stride);
    for pix in without_padding.chunks_mut(4) {
        let a = pix[0];
        pix[0] = pix[2];
        pix[2] = a;
    }
    ::gif::Frame::from_rgba(width, height, &mut without_padding)
}

pub fn from_bgrx_with_stride(width: u16, height: u16, pixels: &mut [u8], stride: usize) -> ::gif::Frame<'static> {
    let mut without_padding = remove_padding(width, pixels, stride);

    for pix in without_padding.chunks_mut(4) {
        let a = pix[0];
        pix[0] = pix[2];
        pix[2] = a;
        pix[3] = 0xFF;
    }
    ::gif::Frame::from_rgba(width, height, &mut without_padding)
}

/// Creates a frame from pixels in RGB format.
///
/// *Note: This method is not optimized for speed.*
pub fn from_bgr_with_stride(width: u16, height: u16, pixels: &[u8], stride: usize) -> ::gif::Frame<'static> {
    let mut without_padding = remove_padding(width, pixels, stride);
    for pix in without_padding.chunks_mut(3) {
        let a = pix[0];
        pix[0] = pix[2];
        pix[2] = a;
    }
    ::gif::Frame::from_rgb(width, height, &mut without_padding)
}
<|endoftext|>