<commit_before>use redox::Box;
use redox::{self, cmp, env};
use redox::collections::BTreeMap;
use redox::fs::{self, File};
use redox::io::{Read, Seek, SeekFrom};
use redox::time::{self, Duration};
use redox::vec::Vec;
use redox::string::{String, ToString};

use orbital::{event, BmpFile, Color, EventOption, MouseEvent, Window};

struct FileType {
    description: String,
    icon: BmpFile,
}

impl FileType {
    fn new(desc: &str, icon: &str) -> FileType {
        FileType { description: desc.to_string(), icon: load_icon(icon) }
    }

}

struct FileTypesInfo {
    file_types: BTreeMap<String, FileType>,
}

impl FileTypesInfo {
    pub fn new () -> FileTypesInfo {
        let mut file_types = BTreeMap::<String, FileType>::new();
        file_types.insert("/".to_string(),
                          FileType::new("Folder", "inode-directory"));
        file_types.insert("wav".to_string(),
                          FileType::new("WAV audio", "audio-x-wav"));
        file_types.insert("bin".to_string(),
                          FileType::new("Executable", "application-x-executable"));
        file_types.insert("bmp".to_string(),
                          FileType::new("Bitmap Image", "image-x-generic"));
        file_types.insert("rs".to_string(),
                          FileType::new("Rust source code", "text-x-makefile"));
        file_types.insert("crate".to_string(),
                          FileType::new("Rust crate", "application-x-archive"));
        file_types.insert("rlib".to_string(),
                          FileType::new("Static Rust library", "application-x-object"));
        file_types.insert("asm".to_string(),
                          FileType::new("Assembly source", "text-x-makefile"));
        file_types.insert("list".to_string(),
                          FileType::new("Disassembly source", "text-x-makefile"));
        file_types.insert("c".to_string(),
                          FileType::new("C source code", "text-x-csrc"));
        file_types.insert("cpp".to_string(),
                          FileType::new("C++ source code", "text-x-c++src"));
        file_types.insert("h".to_string(),
                          FileType::new("C header", "text-x-chdr"));
        file_types.insert("sh".to_string(),
                          FileType::new("Shell script", "text-x-script"));
        file_types.insert("lua".to_string(),
                          FileType::new("Lua script", "text-x-script"));
        file_types.insert("txt".to_string(),
                          FileType::new("Plain text document", "text-x-generic"));
        file_types.insert("md".to_string(),
                          FileType::new("Markdown document", "text-x-generic"));
        file_types.insert("toml".to_string(),
                          FileType::new("TOML document", "text-x-generic"));
        file_types.insert("json".to_string(),
                          FileType::new("JSON document", "text-x-generic"));
        file_types.insert("REDOX".to_string(),
                          FileType::new("Redox package", "text-x-generic"));
        file_types.insert("".to_string(),
                          FileType::new("Unknown file", "unknown"));
        FileTypesInfo { file_types: file_types }
    }

    pub fn description_for(&self, file_name: &str) -> String {
        if file_name.ends_with('/') {
            self.file_types["/"].description.clone()
        } else {
            let pos = file_name.rfind('.').unwrap_or(0) + 1;
            let ext = &file_name[pos..];
            if self.file_types.contains_key(ext) {
                self.file_types[ext].description.clone()
            } else {
                self.file_types[""].description.clone()
            }
        }
    }

    pub fn icon_for(&self, file_name: &str) -> &BmpFile {
        if file_name.ends_with('/') {
            &self.file_types["/"].icon
        } else {
            let pos = file_name.rfind('.').unwrap_or(0) + 1;
            let ext = &file_name[pos..];
            if self.file_types.contains_key(ext) {
                &self.file_types[ext].icon
            } else {
                &self.file_types[""].icon
            }
        }
    }
}

enum FileManagerCommand {
    ChangeDir(String),
    Execute(String),
    Redraw,
    Quit,
}

pub struct FileManager {
    file_types_info: FileTypesInfo,
    files: Vec<String>,
    file_sizes: Vec<String>,
    selected: isize,
    last_mouse_event: MouseEvent,
    click_time: Duration,
    window: Box<Window>,
}

fn load_icon(path: &str) -> BmpFile {
    let mut vec: Vec<u8> = Vec::new();
    if let Some(mut file) = File::open(&("file:///ui/mimetypes/".to_string() + path + ".bmp")) {
        file.read_to_end(&mut vec);
    }
    BmpFile::from_data(&vec)
}

impl FileManager {
    pub fn new() -> Self {
        FileManager {
            file_types_info: FileTypesInfo::new(),
            files: Vec::new(),
            file_sizes: Vec::new(),
            selected: -1,
            last_mouse_event: MouseEvent {
                x: 0,
                y: 0,
                left_button: false,
                middle_button: false,
                right_button: false,
            },
            click_time: Duration::new(0, 0),
            window: Window::new(-1,-1,0,0,"").unwrap(),
        }
    }

    fn draw_content(&mut self) {
        self.window.set(Color::WHITE);

        let mut i = 0;
        let mut row = 0;
        let column = {
            let mut tmp = [0, 0];
            for string in self.files.iter() {
                if tmp[0] < string.len() {
                    tmp[0] = string.len();
                }
            }

            tmp[0] += 1;

            for file_size in self.file_sizes.iter() {
                if tmp[1] < file_size.len() {
                    tmp[1] = file_size.len();
                }
            }

            tmp[1] += tmp[0] + 1;
            tmp
        };
        for (file_name, file_size) in self.files.iter().zip(self.file_sizes.iter()) {
            if i == self.selected {
                let width = self.window.width();
                self.window.rect(0, 32 * row as isize, width, 32, Color::rgba(224, 224, 224, 255));
            }

            let icon = self.file_types_info.icon_for(&file_name);
            self.window.image(0,
                              32 * row as isize,
                              icon.width(),
                              icon.height(),
                              icon.as_slice());

            let mut col = 0;
            for c in file_name.chars() {
                if c == '\n' {
                    col = 0;
                    row += 1;
                } else if c == '\t' {
                    col += 8 - col % 8;
                } else {
                    if col < self.window.width() / 8 && row < self.window.height() / 32 {
                        self.window.char(8 * col as isize + 40,
                                    32 * row as isize + 8,
                                    c,
                                    Color::BLACK);
                        col += 1;
                    }
                }
                if col >= self.window.width() / 8 {
                    col = 0;
                    row += 1;
                }
            }

            col = column[0];

            for c in file_size.chars() {
                if c == '\n' {
                    col = 0;
                    row += 1;
                } else if c == '\t' {
                    col += 8 - col % 8;
                } else {
                    if col < self.window.width() / 8 && row < self.window.height() / 32 {
                        self.window.char(8 * col as isize + 40,
                                    32 * row as isize + 8,
                                    c,
                                    Color::BLACK);
                        col += 1;
                    }
                }
                if col >= self.window.width() / 8 {
                    col = 0;
                    row += 1;
                }
            }

            col = column[1];

            let description = self.file_types_info.description_for(&file_name);
            for c in description.chars() {
                if c == '\n' {
                    col = 0;
                    row += 1;
                } else if c == '\t' {
                    col += 8 - col % 8;
                } else {
                    if col < self.window.width() / 8 && row < self.window.height() / 32 {
                        self.window.char(8 * col as isize + 40,
                                    32 * row as isize + 8,
                                    c,
                                    Color::BLACK);
                        col += 1;
                    }
                }
                if col >= self.window.width() / 8 {
                    col = 0;
                    row += 1;
                }
            }

            row += 1;
            i += 1;
        }

        self.window.sync();
    }

    fn set_path(&mut self, path: &str) {
        let mut width = [48, 48, 48];
        let mut height = 0;
        if let Some(readdir) = fs::read_dir(path) {
            self.files.clear();
            for entry in readdir {
                self.files.push(entry.path().to_string());
                self.file_sizes.push(
                    // When the entry is a folder
                    if entry.path().ends_with('/') {
                        let count = match fs::read_dir(&(path.to_string() + entry.path())) {
                            Some(entry_readdir) => entry_readdir.count(),
                            None => 0
                        };

                        if count == 1 {
                            "1 entry".to_string()
                        } else {
                            format!("{} entries", count)
                        }
                    } else {
                        match File::open(&(path.to_string() + entry.path())) {
                            Some(mut file) => match file.seek(SeekFrom::End(0)) {
                                Some(size) => {
                                    if size >= 1_000_000_000 {
                                        format!("{:.1} GB", (size as f64)/1_000_000_000.0)
                                    } else if size >= 1_000_000 {
                                        format!("{:.1} MB", (size as f64)/1_000_000.0)
                                    } else if size >= 1_000 {
                                        format!("{:.1} KB", (size as f64)/1_000.0)
                                    } else {
                                        format!("{:.1} bytes", size)
                                    }
                                }
                                None => "Failed to seek".to_string()
                            },
                            None => "Failed to open".to_string()
                        }
                    }
                );
                // Unwrapping the last file size will not panic since it has
                // been at least pushed once in the vector
                let description = self.file_types_info.description_for(entry.path());
                width[0] = cmp::max(width[0], 48 + (entry.path().len()) * 8);
                width[1] = cmp::max(width[1], 8 + (self.file_sizes.last().unwrap().len()) * 8);
                width[2] = cmp::max(width[2], 8 + (description.len()) * 8);
            }

            if height < self.files.len() * 32 {
                height = self.files.len() * 32;
            }
        }
        self.window = Window::new(self.window.x(),
                                  self.window.y(),
                                  width.iter().sum(),
                                  height,
                                  &path).unwrap();
        self.draw_content();
    }

    fn event_loop(&mut self) -> Option<FileManagerCommand> {
        let mut redraw = false;
        let mut command = None;
        if let Some(event) = self.window.poll() {
            match event.to_option() {
                EventOption::Key(key_event) => {
                    if key_event.pressed {
                        match key_event.scancode {
                            event::K_ESC => return Some(FileManagerCommand::Quit),
                            event::K_HOME => self.selected = 0,
                            event::K_UP => if self.selected > 0 {
                                self.selected -= 1;
                                redraw = true
                            },
                            event::K_END => self.selected = self.files.len() as isize - 1,
                            event::K_DOWN => if self.selected < self.files.len() as isize - 1 {
                                self.selected += 1;
                            },
                            _ => match key_event.character {
                                '\0' => (),
                                '\n' => {
                                    if self.selected >= 0 &&
                                       self.selected < self.files.len() as isize {
                                        match self.files.get(self.selected as usize) {
                                            Some(file) => {
                                                if file.ends_with('/') {
                                                    command = Some(FileManagerCommand::ChangeDir(file.clone()));
                                                } else {
                                                    command = Some(FileManagerCommand::Execute(file.clone()));
                                                }
                                            },
                                            None => (),
                                        }
                                    }
                                }
                                _ => {
                                    let mut i = 0;
                                    for file in self.files.iter() {
                                        if file.starts_with(key_event.character) {
                                            self.selected = i;
                                            break;
                                        }
                                        i += 1;
                                    }
                                }
                            },
                        }
                        if command.is_none() && redraw {
                            command = Some(FileManagerCommand::Redraw);
                        }
                    }
                }
                EventOption::Mouse(mouse_event) => {
                    let mut redraw = false;
                    let mut i = 0;
                    let mut row = 0;
                    for file in self.files.iter() {
                        let mut col = 0;
                        for c in file.chars() {
                            if mouse_event.y >= 32 * row as isize &&
                               mouse_event.y < 32 * row as isize + 32 {
                                self.selected = i;
                            }

                            if c == '\n' {
                                col = 0;
                                row += 1;
                            } else if c == '\t' {
                                col += 8 - col % 8;
                            } else {
                                if col < self.window.width() / 8 && row < self.window.height() / 32 {
                                    col += 1;
                                }
                            }
                            if col >= self.window.width() / 8 {
                                col = 0;
                                row += 1;
                            }
                        }
                        row += 1;
                        i += 1; }

                    //Check for double click
                    if mouse_event.left_button {
                        let click_time = Duration::realtime();

                        if click_time - self.click_time < Duration::new(0, 500 * time::NANOS_PER_MILLI)
                            && self.last_mouse_event.x == mouse_event.x
                            && self.last_mouse_event.y == mouse_event.y {
                            if self.selected >= 0 && self.selected < self.files.len() as isize {
                                if let Some(file) = self.files.get(self.selected as usize) {
                                    if file.ends_with('/') {
                                        command = Some(FileManagerCommand::ChangeDir(file.clone()));
                                    } else {
                                        command = Some(FileManagerCommand::Execute(file.clone()));
                                    }
                                }
                            }
                            self.click_time = Duration::new(0, 0);
                        } else {
                            self.click_time = click_time;
                        }
                    }
                    self.last_mouse_event = mouse_event;
                }
                EventOption::Quit(quit_event) => command = Some(FileManagerCommand::Quit),
                _ => (),
            }
        }
        command
    }

    fn main(&mut self, path: &str) {
        let mut current_path = path.to_string();
        self.set_path(path);
        while true {
            match self.event_loop() {
                Some(event) => {
                    match event {
                        FileManagerCommand::ChangeDir(dir) => {
                            current_path = current_path + &dir;
                            self.set_path(&current_path);
                        },
                        FileManagerCommand::Execute(cmd) => { File::exec(&(current_path.clone() + &cmd)); } ,
                        FileManagerCommand::Redraw => (),
                        FileManagerCommand::Quit => break,
                    };
                    self.draw_content();
                },
                None => (),
            };
        }

    }
}

pub fn main() {
    match env::args().get(1) {
        Some(arg) => FileManager::new().main(arg),
        None => FileManager::new().main("file:///"),
    }
}
<commit_msg>use static strings, and add TODO reminder<commit_after>use redox::Box;
use redox::{self, cmp, env};
use redox::collections::BTreeMap;
use redox::fs::{self, File};
use redox::io::{Read, Seek, SeekFrom};
use redox::time::{self, Duration};
use redox::vec::Vec;
use redox::string::{String, ToString};

use orbital::{event, BmpFile, Color, EventOption, MouseEvent, Window};

struct FileType {
    description: &'static str,
    icon: BmpFile,
}

impl FileType {
    fn new(desc: &'static str, icon: &str) -> FileType {
        FileType { description: desc, icon: load_icon(icon) }
    }

}

struct FileTypesInfo {
    file_types: BTreeMap<&'static str, FileType>,
}

impl FileTypesInfo {
    pub fn new () -> FileTypesInfo {
        let mut file_types = BTreeMap::<&'static str, FileType>::new();
        file_types.insert("/",
                          FileType::new("Folder", "inode-directory"));
        file_types.insert("wav",
                          FileType::new("WAV audio", "audio-x-wav"));
        file_types.insert("bin",
                          FileType::new("Executable", "application-x-executable"));
        file_types.insert("bmp",
                          FileType::new("Bitmap Image", "image-x-generic"));
        file_types.insert("rs",
                          FileType::new("Rust source code", "text-x-makefile"));
        file_types.insert("crate",
                          FileType::new("Rust crate", "application-x-archive"));
        file_types.insert("rlib",
                          FileType::new("Static Rust library", "application-x-object"));
        file_types.insert("asm",
                          FileType::new("Assembly source", "text-x-makefile"));
        file_types.insert("list",
                          FileType::new("Disassembly source", "text-x-makefile"));
        file_types.insert("c",
                          FileType::new("C source code", "text-x-csrc"));
        file_types.insert("cpp",
                          FileType::new("C++ source code", "text-x-c++src"));
        file_types.insert("h",
                          FileType::new("C header", "text-x-chdr"));
        file_types.insert("sh",
                          FileType::new("Shell script", "text-x-script"));
        file_types.insert("lua",
                          FileType::new("Lua script", "text-x-script"));
        file_types.insert("txt",
                          FileType::new("Plain text document", "text-x-generic"));
        file_types.insert("md",
                          FileType::new("Markdown document", "text-x-generic"));
        file_types.insert("toml",
                          FileType::new("TOML document", "text-x-generic"));
        file_types.insert("json",
                          FileType::new("JSON document", "text-x-generic"));
        file_types.insert("REDOX",
                          FileType::new("Redox package", "text-x-generic"));
        file_types.insert("",
                          FileType::new("Unknown file", "unknown"));
        FileTypesInfo { file_types: file_types }
    }

    pub fn description_for(&self, file_name: &str) -> String {
        if file_name.ends_with('/') {
            self.file_types["/"].description.to_string()
        } else {
            let pos = file_name.rfind('.').unwrap_or(0) + 1;
            let ext = &file_name[pos..];
            if self.file_types.contains_key(ext) {
                self.file_types[ext].description.to_string()
            } else {
                self.file_types[""].description.to_string()
            }
        }
    }

    pub fn icon_for(&self, file_name: &str) -> &BmpFile {
        if file_name.ends_with('/') {
            &self.file_types["/"].icon
        } else {
            let pos = file_name.rfind('.').unwrap_or(0) + 1;
            let ext = &file_name[pos..];
            if self.file_types.contains_key(ext) {
                &self.file_types[ext].icon
            } else {
                &self.file_types[""].icon
            }
        }
    }
}

enum FileManagerCommand {
    ChangeDir(String),
    Execute(String),
    Redraw,
    Quit,
}

pub struct FileManager {
    file_types_info: FileTypesInfo,
    files: Vec<String>,
    file_sizes: Vec<String>,
    selected: isize,
    last_mouse_event: MouseEvent,
    click_time: Duration,
    window: Box<Window>,
}

fn load_icon(path: &str) -> BmpFile {
    let mut vec: Vec<u8> = Vec::new();
    if let Some(mut file) = File::open(&("file:///ui/mimetypes/".to_string() + path + ".bmp")) {
        file.read_to_end(&mut vec);
    }
    BmpFile::from_data(&vec)
}

impl FileManager {
    pub fn new() -> Self {
        FileManager {
            file_types_info: FileTypesInfo::new(),
            files: Vec::new(),
            file_sizes: Vec::new(),
            selected: -1,
            last_mouse_event: MouseEvent {
                x: 0,
                y: 0,
                left_button: false,
                middle_button: false,
                right_button: false,
            },
            click_time: Duration::new(0, 0),
            window: Window::new(-1,-1,0,0,"").unwrap(),
        }
    }

    fn draw_content(&mut self) {
        self.window.set(Color::WHITE);

        let mut i = 0;
        let mut row = 0;
        let column = {
            let mut tmp = [0, 0];
            for string in self.files.iter() {
                if tmp[0] < string.len() {
                    tmp[0] = string.len();
                }
            }

            tmp[0] += 1;

            for file_size in self.file_sizes.iter() {
                if tmp[1] < file_size.len() {
                    tmp[1] = file_size.len();
                }
            }

            tmp[1] += tmp[0] + 1;
            tmp
        };
        for (file_name, file_size) in self.files.iter().zip(self.file_sizes.iter()) {
            if i == self.selected {
                let width = self.window.width();
                self.window.rect(0, 32 * row as isize, width, 32, Color::rgba(224, 224, 224, 255));
            }

            let icon = self.file_types_info.icon_for(&file_name);
            self.window.image(0,
                              32 * row as isize,
                              icon.width(),
                              icon.height(),
                              icon.as_slice());

            let mut col = 0;
            for c in file_name.chars() {
                if c == '\n' {
                    col = 0;
                    row += 1;
                } else if c == '\t' {
                    col += 8 - col % 8;
                } else {
                    if col < self.window.width() / 8 && row < self.window.height() / 32 {
                        self.window.char(8 * col as isize + 40,
                                    32 * row as isize + 8,
                                    c,
                                    Color::BLACK);
                        col += 1;
                    }
                }
                if col >= self.window.width() / 8 {
                    col = 0;
                    row += 1;
                }
            }

            col = column[0];

            for c in file_size.chars() {
                if c == '\n' {
                    col = 0;
                    row += 1;
                } else if c == '\t' {
                    col += 8 - col % 8;
                } else {
                    if col < self.window.width() / 8 && row < self.window.height() / 32 {
                        self.window.char(8 * col as isize + 40,
                                    32 * row as isize + 8,
                                    c,
                                    Color::BLACK);
                        col += 1;
                    }
                }
                if col >= self.window.width() / 8 {
                    col = 0;
                    row += 1;
                }
            }

            col = column[1];

            let description = self.file_types_info.description_for(&file_name);
            for c in description.chars() {
                if c == '\n' {
                    col = 0;
                    row += 1;
                } else if c == '\t' {
                    col += 8 - col % 8;
                } else {
                    if col < self.window.width() / 8 && row < self.window.height() / 32 {
                        self.window.char(8 * col as isize + 40,
                                    32 * row as isize + 8,
                                    c,
                                    Color::BLACK);
                        col += 1;
                    }
                }
                if col >= self.window.width() / 8 {
                    col = 0;
                    row += 1;
                }
            }

            row += 1;
            i += 1;
        }

        self.window.sync();
    }

    fn set_path(&mut self, path: &str) {
        let mut width = [48, 48, 48];
        let mut height = 0;
        if let Some(readdir) = fs::read_dir(path) {
            self.files.clear();
            for entry in readdir {
                self.files.push(entry.path().to_string());
                self.file_sizes.push(
                    // When the entry is a folder
                    if entry.path().ends_with('/') {
                        let count = match fs::read_dir(&(path.to_string() + entry.path())) {
                            Some(entry_readdir) => entry_readdir.count(),
                            None => 0
                        };

                        if count == 1 {
                            "1 entry".to_string()
                        } else {
                            format!("{} entries", count)
                        }
                    } else {
                        match File::open(&(path.to_string() + entry.path())) {
                            Some(mut file) => match file.seek(SeekFrom::End(0)) {
                                Some(size) => {
                                    if size >= 1_000_000_000 {
                                        format!("{:.1} GB", (size as f64)/1_000_000_000.0)
                                    } else if size >= 1_000_000 {
                                        format!("{:.1} MB", (size as f64)/1_000_000.0)
                                    } else if size >= 1_000 {
                                        format!("{:.1} KB", (size as f64)/1_000.0)
                                    } else {
                                        format!("{:.1} bytes", size)
                                    }
                                }
                                None => "Failed to seek".to_string()
                            },
                            None => "Failed to open".to_string()
                        }
                    }
                );
                // Unwrapping the last file size will not panic since it has
                // been at least pushed once in the vector
                let description = self.file_types_info.description_for(entry.path());
                width[0] = cmp::max(width[0], 48 + (entry.path().len()) * 8);
                width[1] = cmp::max(width[1], 8 + (self.file_sizes.last().unwrap().len()) * 8);
                width[2] = cmp::max(width[2], 8 + (description.len()) * 8);
            }

            if height < self.files.len() * 32 {
                height = self.files.len() * 32;
            }
        }
        // TODO: HACK ALERT - should use resize whenver that gets added
        self.window = Window::new(self.window.x(),
                                  self.window.y(),
                                  width.iter().sum(),
                                  height,
                                  &path).unwrap();
        self.draw_content();
    }

    fn event_loop(&mut self) -> Option<FileManagerCommand> {
        let mut redraw = false;
        let mut command = None;
        if let Some(event) = self.window.poll() {
            match event.to_option() {
                EventOption::Key(key_event) => {
                    if key_event.pressed {
                        match key_event.scancode {
                            event::K_ESC => return Some(FileManagerCommand::Quit),
                            event::K_HOME => self.selected = 0,
                            event::K_UP => if self.selected > 0 {
                                self.selected -= 1;
                                redraw = true
                            },
                            event::K_END => self.selected = self.files.len() as isize - 1,
                            event::K_DOWN => if self.selected < self.files.len() as isize - 1 {
                                self.selected += 1;
                            },
                            _ => match key_event.character {
                                '\0' => (),
                                '\n' => {
                                    if self.selected >= 0 &&
                                       self.selected < self.files.len() as isize {
                                        match self.files.get(self.selected as usize) {
                                            Some(file) => {
                                                if file.ends_with('/') {
                                                    command = Some(FileManagerCommand::ChangeDir(file.clone()));
                                                } else {
                                                    command = Some(FileManagerCommand::Execute(file.clone()));
                                                }
                                            },
                                            None => (),
                                        }
                                    }
                                }
                                _ => {
                                    let mut i = 0;
                                    for file in self.files.iter() {
                                        if file.starts_with(key_event.character) {
                                            self.selected = i;
                                            break;
                                        }
                                        i += 1;
                                    }
                                }
                            },
                        }
                        if command.is_none() && redraw {
                            command = Some(FileManagerCommand::Redraw);
                        }
                    }
                }
                EventOption::Mouse(mouse_event) => {
                    let mut redraw = false;
                    let mut i = 0;
                    let mut row = 0;
                    for file in self.files.iter() {
                        let mut col = 0;
                        for c in file.chars() {
                            if mouse_event.y >= 32 * row as isize &&
                               mouse_event.y < 32 * row as isize + 32 {
                                self.selected = i;
                            }

                            if c == '\n' {
                                col = 0;
                                row += 1;
                            } else if c == '\t' {
                                col += 8 - col % 8;
                            } else {
                                if col < self.window.width() / 8 && row < self.window.height() / 32 {
                                    col += 1;
                                }
                            }
                            if col >= self.window.width() / 8 {
                                col = 0;
                                row += 1;
                            }
                        }
                        row += 1;
                        i += 1; }

                    //Check for double click
                    if mouse_event.left_button {
                        let click_time = Duration::realtime();

                        if click_time - self.click_time < Duration::new(0, 500 * time::NANOS_PER_MILLI)
                            && self.last_mouse_event.x == mouse_event.x
                            && self.last_mouse_event.y == mouse_event.y {
                            if self.selected >= 0 && self.selected < self.files.len() as isize {
                                if let Some(file) = self.files.get(self.selected as usize) {
                                    if file.ends_with('/') {
                                        command = Some(FileManagerCommand::ChangeDir(file.clone()));
                                    } else {
                                        command = Some(FileManagerCommand::Execute(file.clone()));
                                    }
                                }
                            }
                            self.click_time = Duration::new(0, 0);
                        } else {
                            self.click_time = click_time;
                        }
                    }
                    self.last_mouse_event = mouse_event;
                }
                EventOption::Quit(quit_event) => command = Some(FileManagerCommand::Quit),
                _ => (),
            }
        }
        command
    }

    fn main(&mut self, path: &str) {
        let mut current_path = path.to_string();
        self.set_path(path);
        while true {
            match self.event_loop() {
                Some(event) => {
                    match event {
                        FileManagerCommand::ChangeDir(dir) => {
                            current_path = current_path + &dir;
                            self.set_path(&current_path);
                        },
                        FileManagerCommand::Execute(cmd) => { File::exec(&(current_path.clone() + &cmd)); } ,
                        FileManagerCommand::Redraw => (),
                        FileManagerCommand::Quit => break,
                    };
                    self.draw_content();
                },
                None => (),
            };
        }

    }
}

pub fn main() {
    match env::args().get(1) {
        Some(arg) => FileManager::new().main(arg),
        None => FileManager::new().main("file:///"),
    }
}
<|endoftext|>


<commit_before>
use dimensions::Dimensions;
use point::Point;
use ui_context::UiContext;

/// Callback params.
pub type WidgetNum = usize;
pub type ColNum = usize;
pub type RowNum = usize;
pub type Width = f64;
pub type Height = f64;
pub type PosX = f64;
pub type PosY = f64;

/// Draw a matrix of any rectangular widget type, where the
/// matrix will provide a callback with the widget number,
/// it's `rows` and `cols` position, the width and height
/// for the widget and the location at which the widget
/// should be drawn.
pub struct WidgetMatrixContext<'a> {
    uic: &'a mut UiContext,
    cols: usize,
    rows: usize,
    pos: Point,
    dim: Dimensions,
    cell_pad_w: f64,
    cell_pad_h: f64,
}

/*
/// A cell to be returned via the cell callback.
pub struct MatrixCell<'a>(&'a mut UiContext, WidgetNum, ColNum, RowNum, PosX, PosY, Width, Height);
*/

impl<'a> WidgetMatrixContext<'a> {

    /// The callback called for each widget in the matrix.
    /// This should be called following all builder methods.
    pub fn each_widget(&'a mut self, callback: Box<Fn(&mut UiContext, WidgetNum, ColNum, RowNum, Point, Dimensions)>) {
        let widget_w = self.dim[0] / self.cols as f64;
        let widget_h = self.dim[1] / self.rows as f64;
        let mut widget_num = 0us;
        for col in range(0us, self.cols) {
            for row in range(0us, self.rows) {
                callback(
                    self.uic,
                    widget_num,
                    col,
                    row,
                    [self.pos[0] + (widget_w * col as f64) + self.cell_pad_w,
                     self.pos[1] + (widget_h * row as f64) + self.cell_pad_h],
                    [widget_w - self.cell_pad_w * 2.0,
                     widget_h - self.cell_pad_h * 2.0],
                );
                widget_num += 1us;
            }
        }
    }

    /// A builder method for adding padding to the cell.
    pub fn cell_padding(self, w: f64, h: f64) -> WidgetMatrixContext<'a> {
        WidgetMatrixContext { cell_pad_w: w, cell_pad_h: h, ..self }
    }

    /*
    /// Create an iterator over the matrix cells.
    fn iter_cells(&mut self) -> CellIterator {
    }
    */

}

/*
/// A struct used for iterating over the cells of a WidgetMatrix.
pub struct CellIterator {
    row: usize,
    col: usize,
    rows: usize,
    cols: usize,
}

impl Iterator for CellIterator {
    fn next
    */

pub trait WidgetMatrixBuilder<'a> {
    /// A widget matrix builder method to be implemented by the UiContext.
    fn widget_matrix(&'a mut self, cols: usize, rows: usize) -> WidgetMatrixContext<'a>;
}

impl<'a> WidgetMatrixBuilder<'a> for UiContext {

    /// Create a widget matrix context.
    fn widget_matrix(&'a mut self, cols: usize, rows: usize) -> WidgetMatrixContext<'a> {
        WidgetMatrixContext {
            uic: self,
            cols: cols,
            rows: rows,
            pos: [0.0, 0.0],
            dim: [256.0, 256.0],
            cell_pad_w: 0.0,
            cell_pad_h: 0.0,
        }
    }
}

impl_positionable!(WidgetMatrixContext,);
impl_shapeable!(WidgetMatrixContext,);
<commit_msg>Have each_widget() take an FnMut, for the example<commit_after>
use dimensions::Dimensions;
use point::Point;
use ui_context::UiContext;

/// Callback params.
pub type WidgetNum = usize;
pub type ColNum = usize;
pub type RowNum = usize;
pub type Width = f64;
pub type Height = f64;
pub type PosX = f64;
pub type PosY = f64;

/// Draw a matrix of any rectangular widget type, where the
/// matrix will provide a callback with the widget number,
/// it's `rows` and `cols` position, the width and height
/// for the widget and the location at which the widget
/// should be drawn.
pub struct WidgetMatrixContext<'a> {
    uic: &'a mut UiContext,
    cols: usize,
    rows: usize,
    pos: Point,
    dim: Dimensions,
    cell_pad_w: f64,
    cell_pad_h: f64,
}

/*
/// A cell to be returned via the cell callback.
pub struct MatrixCell<'a>(&'a mut UiContext, WidgetNum, ColNum, RowNum, PosX, PosY, Width, Height);
*/

impl<'a> WidgetMatrixContext<'a> {

    /// The callback called for each widget in the matrix.
    /// This should be called following all builder methods.
    pub fn each_widget(&'a mut self, mut callback: Box<FnMut(&mut UiContext, WidgetNum, ColNum, RowNum, Point, Dimensions)>) {
        let widget_w = self.dim[0] / self.cols as f64;
        let widget_h = self.dim[1] / self.rows as f64;
        let mut widget_num = 0us;
        for col in range(0us, self.cols) {
            for row in range(0us, self.rows) {
                callback(
                    self.uic,
                    widget_num,
                    col,
                    row,
                    [self.pos[0] + (widget_w * col as f64) + self.cell_pad_w,
                     self.pos[1] + (widget_h * row as f64) + self.cell_pad_h],
                    [widget_w - self.cell_pad_w * 2.0,
                     widget_h - self.cell_pad_h * 2.0],
                );
                widget_num += 1us;
            }
        }
    }

    /// A builder method for adding padding to the cell.
    pub fn cell_padding(self, w: f64, h: f64) -> WidgetMatrixContext<'a> {
        WidgetMatrixContext { cell_pad_w: w, cell_pad_h: h, ..self }
    }

    /*
    /// Create an iterator over the matrix cells.
    fn iter_cells(&mut self) -> CellIterator {
    }
    */

}

/*
/// A struct used for iterating over the cells of a WidgetMatrix.
pub struct CellIterator {
    row: usize,
    col: usize,
    rows: usize,
    cols: usize,
}

impl Iterator for CellIterator {
    fn next
    */

pub trait WidgetMatrixBuilder<'a> {
    /// A widget matrix builder method to be implemented by the UiContext.
    fn widget_matrix(&'a mut self, cols: usize, rows: usize) -> WidgetMatrixContext<'a>;
}

impl<'a> WidgetMatrixBuilder<'a> for UiContext {

    /// Create a widget matrix context.
    fn widget_matrix(&'a mut self, cols: usize, rows: usize) -> WidgetMatrixContext<'a> {
        WidgetMatrixContext {
            uic: self,
            cols: cols,
            rows: rows,
            pos: [0.0, 0.0],
            dim: [256.0, 256.0],
            cell_pad_w: 0.0,
            cell_pad_h: 0.0,
        }
    }
}

impl_positionable!(WidgetMatrixContext,);
impl_shapeable!(WidgetMatrixContext,);
<|endoftext|>


<commit_before>mod ui;

use crate::config::Config;
use crate::err::{self, Result};
use crate::file::{SaveDir, SaveFile};
use crate::track::{EntryState, SeriesTracker};
use crate::util;
use crate::LastWatched;
use anime::remote::{RemoteService, SeriesInfo};
use anime::{SeasonInfoList, Series};
use chrono::{DateTime, Duration, Utc};
use clap::ArgMatches;
use snafu::{OptionExt, ResultExt};
use std::borrow::Cow;
use std::ops::Add;
use std::process;
use termion::event::Key;
use ui::{Event, Events, UI};

pub fn run(args: &ArgMatches) -> Result<()> {
    let cstate = {
        let config = crate::get_config()?;
        let remote = crate::get_remote(args, true)?;

        CommonState::new(args, config, remote)
    };

    let mut state = {
        let name = crate::get_series_name(args)?;
        let series_names = SaveDir::LocalData.get_subdirs()?;
        let selected_series = series_names.iter().position(|s| *s == name).unwrap_or(0);

        UIState {
            series: SeriesState::new(&cstate, &name, true)?,
            series_names,
            selected_series,
            selection: Selection::Series,
        }
    };

    let mut ui = UI::init()?;
    let events = Events::new(Duration::seconds(1));

    loop {
        ui.draw(&state)?;

        let series = &mut state.series;
        let season = &mut series.season;

        let remote = cstate.remote.as_ref();
        let config = &cstate.config;

        match events.next()? {
            Event::Input(key) => match key {
                // Exit
                Key::Char('q') => {
                    // Prevent ruining the user's terminal
                    ui.clear().ok();
                    break Ok(());
                }
                // Sync list entry from / to remote
                Key::Char(ch @ 'r') | Key::Char(ch @ 's') => {
                    let name = &series.name;
                    let entry = &mut season.tracker.state;

                    if ch == 'r' {
                        ui.log_capture("Syncing entry from remote", || {
                            entry.force_sync_changes_from_remote(remote, name)
                        });
                    } else if ch == 's' {
                        ui.log_capture("Syncing entry to remote", || {
                            entry.force_sync_changes_to_remote(remote, name)
                        });
                    }

                    season.update_value_cache(remote);
                }
                // Play next episode
                Key::Char('\n') => {
                    // Update the last watched series
                    if !series.is_last_watched {
                        let name = &series.name;

                        ui.log_capture("Marking as the last watched series", || {
                            let last_watched = LastWatched::new(name);
                            last_watched.save(None)
                        });

                        series.is_last_watched = true;
                    }

                    ui.log_capture("Playing next episode", || {
                        season.play_next_episode_async(&cstate)
                    });
                }
                // Switch between series and season selection
                Key::Left | Key::Right if state.is_idle() => {
                    state.selection.set_opposite();
                }
                // Select series
                Key::Up | Key::Down if state.can_select_series() => {
                    let next_index = if key == Key::Up {
                        state.selected_series.saturating_sub(1)
                    } else {
                        state.selected_series + 1
                    };

                    state.select_series(next_index, &cstate)?;
                }
                // Select season
                Key::Up | Key::Down if state.can_select_season() => {
                    let new_season = {
                        let cur_season = state.series.season.season_num;

                        if key == Key::Up {
                            cur_season.saturating_sub(1)
                        } else {
                            cur_season + 1
                        }
                    };

                    state.series = state.series.set_season(new_season, remote)?;
                }
                _ => (),
            },
            Event::Tick => match &mut season.watch_state {
                WatchState::Idle => (),
                WatchState::Watching(start_time, _, child) => {
                    let status = match child.try_wait().context(err::IO)? {
                        Some(status) => status,
                        None => continue,
                    };

                    if !status.success() {
                        ui.push_log_status("Player did not exit properly");
                        continue;
                    }

                    let mins_watched = {
                        let watch_time = Utc::now() - *start_time;
                        watch_time.num_seconds() as f32 / 60.0
                    };

                    let mins_must_watch =
                        season.series.info.episode_length as f32 * config.episode.pcnt_must_watch;

                    if mins_watched >= mins_must_watch {
                        ui.log_capture("Marking episode as completed", || {
                            season.tracker.episode_completed(remote, config)
                        });
                    } else {
                        ui.push_log_status("Not marking episode as completed");
                    }

                    season.watch_state = WatchState::Idle;
                    season.update_value_cache(remote);
                }
            },
        }
    }
}

struct CommonState<'a> {
    args: &'a ArgMatches<'a>,
    config: Config,
    remote: Box<RemoteService>,
}

impl<'a> CommonState<'a> {
    fn new(
        args: &'a ArgMatches<'a>,
        config: Config,
        remote: Box<RemoteService>,
    ) -> CommonState<'a> {
        CommonState {
            args,
            config,
            remote,
        }
    }
}

pub struct UIState<'a> {
    pub series: SeriesState<'a>,
    pub series_names: Vec<String>,
    pub selected_series: usize,
    pub selection: Selection,
}

impl<'a> UIState<'a> {
    fn select_series(&mut self, index: usize, cstate: &'a CommonState) -> Result<()> {
        let new_name = match self.series_names.get(index) {
            Some(new_name) => new_name,
            None => return Ok(()),
        };

        self.series = SeriesState::new(cstate, new_name, false)?;
        self.selected_series = index;

        Ok(())
    }

    fn is_idle(&self) -> bool {
        self.series.season.watch_state == WatchState::Idle
    }

    fn can_select_season(&self) -> bool {
        self.selection == Selection::Season && self.is_idle()
    }

    fn can_select_series(&self) -> bool {
        self.selection == Selection::Series && self.is_idle()
    }
}

#[derive(PartialEq, Copy, Clone)]
pub enum Selection {
    Series,
    Season,
}

impl Selection {
    fn opposite(self) -> Selection {
        match self {
            Selection::Series => Selection::Season,
            Selection::Season => Selection::Series,
        }
    }

    fn set_opposite(&mut self) {
        *self = self.opposite();
    }
}

pub type ProgressTime = DateTime<Utc>;
pub type StartTime = DateTime<Utc>;

pub enum WatchState {
    Idle,
    Watching(StartTime, ProgressTime, process::Child),
}

impl PartialEq for WatchState {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (WatchState::Idle, WatchState::Idle) => true,
            (WatchState::Watching(_, _, _), WatchState::Watching(_, _, _)) => true,
            _ => false,
        }
    }
}

pub struct SeriesState<'a> {
    pub name: String,
    pub season: SeasonState<'a>,
    pub seasons: SeasonInfoList,
    pub num_seasons: usize,
    pub is_last_watched: bool,
}

impl<'a> SeriesState<'a> {
    fn new<S>(cstate: &'a CommonState, name: S, is_last_watched: bool) -> Result<SeriesState<'a>>
    where
        S: Into<String>,
    {
        let name = name.into();
        let remote = cstate.remote.as_ref();

        let episodes = crate::get_episodes(&cstate.args, &name, &cstate.config)?;
        let seasons = crate::get_season_list(&name, remote, &episodes)?;
        let num_seasons = seasons.len();
        let season_num = crate::get_season_num(&cstate.args);
        let series = Series::from_season_list(&seasons, season_num, episodes)?;
        let season = SeasonState::new(remote, &name, series, season_num)?;

        Ok(SeriesState {
            name,
            season,
            seasons,
            num_seasons,
            is_last_watched,
        })
    }

    /// Returns the same `SeriesState` with new `SeasonState` data if `season_num`
    /// points to an existing season. Otherwise, it is the same `SeriesState` unmodified.
    ///
    /// This method consumes the `SeriesState` to avoid cloning data.
    fn set_season<R>(mut self, season_num: usize, remote: &'a R) -> Result<SeriesState>
    where
        R: RemoteService + ?Sized,
    {
        if !self.seasons.has(season_num) {
            return Ok(self);
        }

        self.season = self
            .season
            .new_in_place(remote, &self.name, &self.seasons, season_num)?;

        Ok(self)
    }
}

pub struct SeasonState<'a> {
    pub series: Series<'a>,
    pub tracker: SeriesTracker<'a>,
    pub value_cache: SeasonValueCache<'a>,
    pub season_num: usize,
    pub watch_state: WatchState,
}

impl<'a> SeasonState<'a> {
    fn new<R, S>(
        remote: &'a R,
        name: S,
        series: Series<'a>,
        season_num: usize,
    ) -> Result<SeasonState<'a>>
    where
        R: RemoteService + ?Sized,
        S: Into<String>,
    {
        let tracker = SeriesTracker::init(series.info.clone(), name)?;
        let value_cache = SeasonValueCache::new(remote, &tracker);

        Ok(SeasonState {
            series,
            tracker,
            value_cache,
            season_num,
            watch_state: WatchState::Idle,
        })
    }

    /// Returns a new `SeasonState` using some existing data from the consumed one.
    ///
    /// This method consumes the `SeasonState` so it can reuse existing episode data.
    fn new_in_place<R, S>(
        self,
        remote: &'a R,
        name: S,
        seasons: &SeasonInfoList,
        season_num: usize,
    ) -> Result<SeasonState<'a>>
    where
        R: RemoteService + ?Sized,
        S: Into<String>,
    {
        let episodes = self.series.episodes;
        let series = Series::from_season_list(seasons, season_num, episodes)?;

        SeasonState::new(remote, name, series, season_num)
    }

    fn play_next_episode_async(&mut self, cstate: &CommonState) -> Result<()> {
        let remote = cstate.remote.as_ref();
        let config = &cstate.config;

        self.tracker.begin_watching(remote, config)?;
        let next_ep = self.tracker.state.watched_eps() + 1;

        let episode = self
            .series
            .get_episode(next_ep)
            .context(err::EpisodeNotFound { episode: next_ep })?;

        let start_time = Utc::now();

        let child = crate::process::open_with_default(episode)
            .context(err::FailedToPlayEpisode { episode: next_ep })?;

        let progress_time = {
            let secs_must_watch =
                (self.series.info.episode_length as f32 * config.episode.pcnt_must_watch) * 60.0;
            let time_must_watch = Duration::seconds(secs_must_watch as i64);

            start_time.add(time_must_watch)
        };

        self.watch_state = WatchState::Watching(start_time, progress_time, child);

        Ok(())
    }

    fn update_value_cache<R>(&mut self, remote: &'a R)
    where
        R: RemoteService + ?Sized,
    {
        self.value_cache.update(remote, &self.tracker);
    }
}

pub struct SeasonValueCache<'a> {
    pub progress: String,
    pub score: Cow<'a, str>,
    pub start_date: Cow<'a, str>,
    pub end_date: Cow<'a, str>,
    pub watch_time_left: String,
    // The following fields will not change
    pub watch_time: String,
    pub episode_length: String,
}

impl<'a> SeasonValueCache<'a> {
    pub fn new<R>(remote: &'a R, tracker: &SeriesTracker<'a>) -> SeasonValueCache<'a>
    where
        R: RemoteService + ?Sized,
    {
        let info = &tracker.info;
        let entry = &tracker.state;

        let watch_time = {
            let watch_time_mins = info.episodes * info.episode_length;
            util::hm_from_mins(watch_time_mins as f32)
        };

        let episode_length = format!("{}M", info.episode_length);

        SeasonValueCache {
            progress: SeasonValueCache::progress(info, entry),
            score: SeasonValueCache::score(remote, entry),
            start_date: SeasonValueCache::start_date(entry),
            end_date: SeasonValueCache::end_date(entry),
            watch_time_left: SeasonValueCache::watch_time_left(info, entry),
            watch_time,
            episode_length,
        }
    }

    pub fn update<R>(&mut self, remote: &'a R, tracker: &SeriesTracker<'a>)
    where
        R: RemoteService + ?Sized,
    {
        let info = &tracker.info;
        let entry = &tracker.state;

        self.progress = SeasonValueCache::progress(info, entry);
        self.score = SeasonValueCache::score(remote, entry);
        self.start_date = SeasonValueCache::start_date(entry);
        self.end_date = SeasonValueCache::end_date(entry);
        self.watch_time_left = SeasonValueCache::watch_time_left(info, entry);
    }

    fn progress(info: &SeriesInfo, entry: &EntryState) -> String {
        format!("{}|{}", entry.watched_eps(), info.episodes)
    }

    fn score<R>(remote: &'a R, entry: &EntryState) -> Cow<'a, str>
    where
        R: RemoteService + ?Sized,
    {
        match entry.score() {
            Some(score) => remote.score_to_str(score),
            None => "??".into(),
        }
    }

    fn start_date(entry: &EntryState) -> Cow<'a, str> {
        match entry.start_date() {
            Some(date) => format!("{}", date.format("%D")).into(),
            None => "??".into(),
        }
    }

    fn end_date(entry: &EntryState) -> Cow<'a, str> {
        match entry.end_date() {
            Some(date) => format!("{}", date.format("%D")).into(),
            None => "??".into(),
        }
    }

    fn watch_time_left(info: &SeriesInfo, entry: &EntryState) -> String {
        let time_left_mins = (info.episodes - entry.watched_eps()) * info.episode_length;
        util::hm_from_mins(time_left_mins as f32)
    }
}
<commit_msg>[anup] Refactor TUI event loop into several functions<commit_after>mod ui;

use crate::config::Config;
use crate::err::{self, Result};
use crate::file::{SaveDir, SaveFile};
use crate::track::{EntryState, SeriesTracker};
use crate::util;
use crate::LastWatched;
use anime::remote::{RemoteService, SeriesInfo};
use anime::{SeasonInfoList, Series};
use chrono::{DateTime, Duration, Utc};
use clap::ArgMatches;
use snafu::{OptionExt, ResultExt};
use std::borrow::Cow;
use std::ops::Add;
use std::process;
use termion::event::Key;
use ui::{Event, Events, UI};

pub fn run(args: &ArgMatches) -> Result<()> {
    let cstate = {
        let config = crate::get_config()?;
        let remote = crate::get_remote(args, true)?;

        CommonState {
            args,
            config,
            remote,
        }
    };

    let mut ui_state = {
        let name = crate::get_series_name(args)?;
        let series_names = SaveDir::LocalData.get_subdirs()?;
        let selected_series = series_names.iter().position(|s| *s == name).unwrap_or(0);

        UIState {
            series: SeriesState::new(&cstate, &name, true)?,
            series_names,
            selected_series,
            selection: Selection::Series,
        }
    };

    let mut ui = UI::init()?;
    let events = Events::new(Duration::seconds(1));

    loop {
        ui.draw(&ui_state)?;

        match events.next()? {
            Event::Input(key) => match key {
                // Exit
                Key::Char('q') => {
                    // Prevent ruining the user's terminal
                    ui.clear().ok();
                    break Ok(());
                }
                key => {
                    ui_state = ui_state.process_key(&cstate, &mut ui, key)?;
                }
            },
            Event::Tick => ui_state.process_tick(&cstate, &mut ui)?,
        }
    }
}

/// Items that are not tied to the UI and are commonly used together.
struct CommonState<'a> {
    args: &'a ArgMatches<'a>,
    config: Config,
    remote: Box<RemoteService>,
}

/// Current state of the UI.
pub struct UIState<'a> {
    series: SeriesState<'a>,
    series_names: Vec<String>,
    selected_series: usize,
    selection: Selection,
}

impl<'a> UIState<'a> {
    fn process_key<B>(
        mut self,
        state: &'a CommonState,
        ui: &mut UI<B>,
        key: Key,
    ) -> Result<UIState<'a>>
    where
        B: tui::backend::Backend,
    {
        match key {
            // Sync list entry from / to remote
            Key::Char(ch @ 'r') | Key::Char(ch @ 's') => {
                let remote = state.remote.as_ref();

                let name = &self.series.name;
                let season = &mut self.series.season;
                let entry = &mut season.tracker.state;

                if ch == 'r' {
                    ui.log_capture("Syncing entry from remote", || {
                        entry.force_sync_changes_from_remote(remote, name)
                    });
                } else if ch == 's' {
                    ui.log_capture("Syncing entry to remote", || {
                        entry.force_sync_changes_to_remote(remote, name)
                    });
                }

                season.update_value_cache(remote);
            }
            // Play next episode
            Key::Char('\n') => {
                self.series.set_last_watched(ui);

                ui.log_capture("Playing next episode", || {
                    self.series.season.play_next_episode_async(&state)
                });
            }
            // Switch between series and season selection
            Key::Left | Key::Right if self.is_idle() => {
                self.selection.set_opposite();
            }
            // Select series
            Key::Up | Key::Down if self.can_select_series() => {
                let index = UIState::next_arrow_key_value(key, self.selected_series);

                let new_name = match self.series_names.get(index) {
                    Some(new_name) => new_name,
                    None => return Ok(self),
                };

                self.series = SeriesState::new(state, new_name, false)?;
                self.selected_series = index;
            }
            // Select season
            Key::Up | Key::Down if self.can_select_season() => {
                let remote = state.remote.as_ref();
                let new_season = UIState::next_arrow_key_value(key, self.series.season.season_num);
                self.series = self.series.set_season(new_season, remote)?;
            }
            _ => (),
        }

        Ok(self)
    }

    fn process_tick<B>(&mut self, state: &'a CommonState, ui: &mut UI<B>) -> Result<()>
    where
        B: tui::backend::Backend,
    {
        self.series.season.process_tick(state, ui)
    }

    fn next_arrow_key_value(key: Key, value: usize) -> usize {
        match key {
            Key::Up => value.saturating_sub(1),
            Key::Down => value + 1,
            _ => value,
        }
    }

    fn is_idle(&self) -> bool {
        self.series.season.watch_state == WatchState::Idle
    }

    fn can_select_season(&self) -> bool {
        self.selection == Selection::Season && self.is_idle()
    }

    fn can_select_series(&self) -> bool {
        self.selection == Selection::Series && self.is_idle()
    }
}

#[derive(PartialEq, Copy, Clone)]
pub enum Selection {
    Series,
    Season,
}

impl Selection {
    fn opposite(self) -> Selection {
        match self {
            Selection::Series => Selection::Season,
            Selection::Season => Selection::Series,
        }
    }

    fn set_opposite(&mut self) {
        *self = self.opposite();
    }
}

pub type ProgressTime = DateTime<Utc>;
pub type StartTime = DateTime<Utc>;

pub enum WatchState {
    Idle,
    Watching(StartTime, ProgressTime, process::Child),
}

impl PartialEq for WatchState {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (WatchState::Idle, WatchState::Idle) => true,
            (WatchState::Watching(_, _, _), WatchState::Watching(_, _, _)) => true,
            _ => false,
        }
    }
}

pub struct SeriesState<'a> {
    pub name: String,
    pub season: SeasonState<'a>,
    pub seasons: SeasonInfoList,
    pub num_seasons: usize,
    pub is_last_watched: bool,
}

impl<'a> SeriesState<'a> {
    fn new<S>(state: &'a CommonState, name: S, is_last_watched: bool) -> Result<SeriesState<'a>>
    where
        S: Into<String>,
    {
        let name = name.into();
        let remote = state.remote.as_ref();

        let episodes = crate::get_episodes(&state.args, &name, &state.config)?;
        let seasons = crate::get_season_list(&name, remote, &episodes)?;
        let num_seasons = seasons.len();
        let season_num = crate::get_season_num(&state.args);
        let series = Series::from_season_list(&seasons, season_num, episodes)?;
        let season = SeasonState::new(remote, &name, series, season_num)?;

        Ok(SeriesState {
            name,
            season,
            seasons,
            num_seasons,
            is_last_watched,
        })
    }

    /// Returns the same `SeriesState` with new `SeasonState` data if `season_num`
    /// points to an existing season. Otherwise, it is the same `SeriesState` unmodified.
    ///
    /// This method consumes the `SeriesState` to avoid cloning data.
    fn set_season<R>(mut self, season_num: usize, remote: &'a R) -> Result<SeriesState>
    where
        R: RemoteService + ?Sized,
    {
        if !self.seasons.has(season_num) {
            return Ok(self);
        }

        self.season = self
            .season
            .new_in_place(remote, &self.name, &self.seasons, season_num)?;

        Ok(self)
    }

    /// Sets the current series as the last watched one if it isn't already.
    fn set_last_watched<B>(&mut self, ui: &mut UI<B>)
    where
        B: tui::backend::Backend,
    {
        if self.is_last_watched {
            return;
        }

        ui.log_capture("Marking as the last watched series", || {
            let last_watched = LastWatched::new(&self.name);
            last_watched.save(None)
        });

        self.is_last_watched = true;
    }
}

pub struct SeasonState<'a> {
    pub series: Series<'a>,
    pub tracker: SeriesTracker<'a>,
    pub value_cache: SeasonValueCache<'a>,
    pub season_num: usize,
    pub watch_state: WatchState,
}

impl<'a> SeasonState<'a> {
    fn new<R, S>(
        remote: &'a R,
        name: S,
        series: Series<'a>,
        season_num: usize,
    ) -> Result<SeasonState<'a>>
    where
        R: RemoteService + ?Sized,
        S: Into<String>,
    {
        let tracker = SeriesTracker::init(series.info.clone(), name)?;
        let value_cache = SeasonValueCache::new(remote, &tracker);

        Ok(SeasonState {
            series,
            tracker,
            value_cache,
            season_num,
            watch_state: WatchState::Idle,
        })
    }

    /// Returns a new `SeasonState` using some existing data from the consumed one.
    ///
    /// This method consumes the `SeasonState` so it can reuse existing episode data.
    fn new_in_place<R, S>(
        self,
        remote: &'a R,
        name: S,
        seasons: &SeasonInfoList,
        season_num: usize,
    ) -> Result<SeasonState<'a>>
    where
        R: RemoteService + ?Sized,
        S: Into<String>,
    {
        let episodes = self.series.episodes;
        let series = Series::from_season_list(seasons, season_num, episodes)?;

        SeasonState::new(remote, name, series, season_num)
    }

    fn play_next_episode_async(&mut self, state: &CommonState) -> Result<()> {
        let remote = state.remote.as_ref();
        let config = &state.config;

        self.tracker.begin_watching(remote, config)?;
        let next_ep = self.tracker.state.watched_eps() + 1;

        let episode = self
            .series
            .get_episode(next_ep)
            .context(err::EpisodeNotFound { episode: next_ep })?;

        let start_time = Utc::now();

        let child = crate::process::open_with_default(episode)
            .context(err::FailedToPlayEpisode { episode: next_ep })?;

        let progress_time = {
            let secs_must_watch =
                (self.series.info.episode_length as f32 * config.episode.pcnt_must_watch) * 60.0;
            let time_must_watch = Duration::seconds(secs_must_watch as i64);

            start_time.add(time_must_watch)
        };

        self.watch_state = WatchState::Watching(start_time, progress_time, child);

        Ok(())
    }

    fn process_tick<B>(&mut self, state: &'a CommonState, ui: &mut UI<B>) -> Result<()>
    where
        B: tui::backend::Backend,
    {
        match &mut self.watch_state {
            WatchState::Idle => (),
            WatchState::Watching(start_time, _, child) => {
                let status = match child.try_wait().context(err::IO)? {
                    Some(status) => status,
                    None => return Ok(()),
                };

                if !status.success() {
                    ui.push_log_status("Player did not exit properly");
                    return Ok(());
                }

                let mins_watched = {
                    let watch_time = Utc::now() - *start_time;
                    watch_time.num_seconds() as f32 / 60.0
                };

                let remote = state.remote.as_ref();
                let config = &state.config;

                let mins_must_watch =
                    self.series.info.episode_length as f32 * config.episode.pcnt_must_watch;

                if mins_watched >= mins_must_watch {
                    ui.log_capture("Marking episode as completed", || {
                        self.tracker.episode_completed(remote, config)
                    });
                } else {
                    ui.push_log_status("Not marking episode as completed");
                }

                self.watch_state = WatchState::Idle;
                self.update_value_cache(remote);
            }
        }

        Ok(())
    }

    fn update_value_cache<R>(&mut self, remote: &'a R)
    where
        R: RemoteService + ?Sized,
    {
        self.value_cache.update(remote, &self.tracker);
    }
}

pub struct SeasonValueCache<'a> {
    pub progress: String,
    pub score: Cow<'a, str>,
    pub start_date: Cow<'a, str>,
    pub end_date: Cow<'a, str>,
    pub watch_time_left: String,
    // The following fields will not change
    pub watch_time: String,
    pub episode_length: String,
}

impl<'a> SeasonValueCache<'a> {
    pub fn new<R>(remote: &'a R, tracker: &SeriesTracker<'a>) -> SeasonValueCache<'a>
    where
        R: RemoteService + ?Sized,
    {
        let info = &tracker.info;
        let entry = &tracker.state;

        let watch_time = {
            let watch_time_mins = info.episodes * info.episode_length;
            util::hm_from_mins(watch_time_mins as f32)
        };

        let episode_length = format!("{}M", info.episode_length);

        SeasonValueCache {
            progress: SeasonValueCache::progress(info, entry),
            score: SeasonValueCache::score(remote, entry),
            start_date: SeasonValueCache::start_date(entry),
            end_date: SeasonValueCache::end_date(entry),
            watch_time_left: SeasonValueCache::watch_time_left(info, entry),
            watch_time,
            episode_length,
        }
    }

    pub fn update<R>(&mut self, remote: &'a R, tracker: &SeriesTracker<'a>)
    where
        R: RemoteService + ?Sized,
    {
        let info = &tracker.info;
        let entry = &tracker.state;

        self.progress = SeasonValueCache::progress(info, entry);
        self.score = SeasonValueCache::score(remote, entry);
        self.start_date = SeasonValueCache::start_date(entry);
        self.end_date = SeasonValueCache::end_date(entry);
        self.watch_time_left = SeasonValueCache::watch_time_left(info, entry);
    }

    fn progress(info: &SeriesInfo, entry: &EntryState) -> String {
        format!("{}|{}", entry.watched_eps(), info.episodes)
    }

    fn score<R>(remote: &'a R, entry: &EntryState) -> Cow<'a, str>
    where
        R: RemoteService + ?Sized,
    {
        match entry.score() {
            Some(score) => remote.score_to_str(score),
            None => "??".into(),
        }
    }

    fn start_date(entry: &EntryState) -> Cow<'a, str> {
        match entry.start_date() {
            Some(date) => format!("{}", date.format("%D")).into(),
            None => "??".into(),
        }
    }

    fn end_date(entry: &EntryState) -> Cow<'a, str> {
        match entry.end_date() {
            Some(date) => format!("{}", date.format("%D")).into(),
            None => "??".into(),
        }
    }

    fn watch_time_left(info: &SeriesInfo, entry: &EntryState) -> String {
        let time_left_mins = (info.episodes - entry.watched_eps()) * info.episode_length;
        util::hm_from_mins(time_left_mins as f32)
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::mem;
use serde_xml;

use credentials::Credentials;
use command::Command;
use region::Region;
use request::{Request, Headers, Query};
use serde_types::{ListBucketResult, AwsError};

use error::{S3Result, ErrorKind};

/// # Example
/// ```
/// use s3::bucket::Bucket;
/// use s3::credentials::Credentials;
///
/// let bucket_name = "rust-s3-test";
/// let region = "us-east-1".parse().unwrap();
/// let credentials = Credentials::new("access_key", "secret_key", None);
///
/// let bucket = Bucket::new(bucket_name, region, credentials);
/// ```
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Bucket {
    pub name: String,
    pub region: Region,
    pub credentials: Credentials,
    pub extra_headers: Headers,
    pub extra_query: Query,
}

impl Bucket {
    /// Instantiate a new `Bucket`.
    ///
    /// # Example
    /// ```
    /// use s3::bucket::Bucket;
    /// use s3::credentials::Credentials;
    ///
    /// let bucket_name = "rust-s3-test";
    /// let region = "us-east-1".parse().unwrap();
    /// let credentials = Credentials::new("access_key", "secret_key", None);
    ///
    /// let bucket = Bucket::new(bucket_name, region, credentials);
    /// ```
    pub fn new(name: &str, region: Region, credentials: Credentials) -> Bucket {
        Bucket {
            name: name.into(),
            region: region,
            credentials: credentials,
            extra_headers: HashMap::new(),
            extra_query: HashMap::new(),
        }
    }

    /// Gets file from an S3 path.
    ///
    /// # Example:
    ///
    /// ```rust,no_run
    /// use s3::bucket::Bucket;
    /// use s3::credentials::Credentials;
    ///
    /// let bucket_name = "rust-s3-test";
    /// let region = "us-east-1".parse().unwrap();
    /// let credentials = Credentials::new("access_key", "secret_key", None);
    /// let bucket = Bucket::new(bucket_name, region, credentials);
    ///
    /// let (data, code) = bucket.get("/test.file").unwrap();
    /// println!("Code: {}\nData: {:?}", code, data);
    /// ```
    pub fn get(&self, path: &str) -> S3Result<(Vec<u8>, u32)> {
        let command = Command::Get;
        let request = Request::new(self, path, command);
        request.execute()
    }

    /// Delete file from an S3 path.
    ///
    /// # Example:
    ///
    /// ```rust,no_run
    /// use s3::bucket::Bucket;
    /// use s3::credentials::Credentials;
    ///
    /// let bucket_name = &"rust-s3-test";
    /// let region = "us-east-1".parse().unwrap();
    /// let credentials = Credentials::new("access_key", "secret_key", None);
    /// let bucket = Bucket::new(bucket_name, region, credentials);
    ///
    /// let (_, code) = bucket.delete("/test.file").unwrap();
    /// assert_eq!(204, code);
    /// ```
    pub fn delete(&self, path: &str) -> S3Result<(Vec<u8>, u32)> {
        let command = Command::Delete;
        let request = Request::new(self, path, command);
        request.execute()
    }

    /// Put into an S3 bucket.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use s3::bucket::Bucket;
    /// use s3::credentials::Credentials;
    ///
    /// let bucket_name = &"rust-s3-test";
    /// let aws_access = &"access_key";
    /// let aws_secret = &"secret_key";
    ///
    /// let bucket_name = &"rust-s3-test";
    /// let region = "us-east-1".parse().unwrap();
    /// let credentials = Credentials::new("access_key", "secret_key", None);
    /// let bucket = Bucket::new(bucket_name, region, credentials);
    ///
    /// let content = "I want to go to S3".as_bytes();
    /// let (_, code) = bucket.put("/test.file", content, "text/plain").unwrap();
    /// assert_eq!(201, code);
    /// ```
    pub fn put(&self, path: &str, data: &[u8], content_type: &str) -> S3Result<(Vec<u8>, u32)> {
        let command = Command::Put {
            content: data,
            content_type: content_type,
        };
        let request = Request::new(self, path, command);
        request.execute()
    }

    /// List the contents of an S3 bucket.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use std::str;
    /// use s3::bucket::Bucket;
    /// use s3::credentials::Credentials;
    /// let bucket_name = &"rust-s3-test";
    /// let aws_access = &"access_key";
    /// let aws_secret = &"secret_key";
    ///
    /// let bucket_name = &"rust-s3-test";
    /// let region = "us-east-1".parse().unwrap();
    /// let credentials = Credentials::new("access_key", "secret_key", None);
    /// let bucket = Bucket::new(bucket_name, region, credentials);
    ///
    /// let (list, code) = bucket.list("/", Some("/")).unwrap();
    /// assert_eq!(200, code);
    /// println!("{:?}", list);
    /// ```
    pub fn list(&self, prefix: &str, delimiter: Option<&str>) -> S3Result<(ListBucketResult, u32)> {
        let command = Command::List {
            prefix: prefix,
            delimiter: delimiter,
        };
        let request = Request::new(self, "/", command);
        let result = request.execute()?;
        let result_string = String::from_utf8_lossy(&result.0);
        if 200 <= result.1 && result.1 < 300 {
            let deserialized: ListBucketResult = serde_xml::deserialize(result_string.as_bytes())?;
            Ok((deserialized, result.1))
        } else {
            let deserialized: AwsError = serde_xml::deserialize(result_string.as_bytes())?;
            let err = ErrorKind::AwsError{
                info: deserialized,
                status: result.1,
                body: result_string.into_owned() };
            Err(err.into())
        }
    }

    /// Get a reference to the name of the S3 bucket.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Get a reference to the hostname of the S3 API endpoint.
    pub fn host(&self) -> &str {
        self.region.endpoint()
    }

    /// Get the region this object will connect to.
    pub fn region(&self) -> Region {
        self.region
    }

    /// Get a reference to the AWS access key.
    pub fn access_key(&self) -> &str {
        &self.credentials.access_key
    }

    /// Get a reference to the AWS secret key.
    pub fn secret_key(&self) -> &str {
        &self.credentials.secret_key
    }

    /// Get a reference to the AWS token.
    pub fn token(&self) -> Option<&str> {
        self.credentials.token.as_ref().map(|s| s.as_str())
    }

    /// Get a reference to the full [`Credentials`](struct.Credentials.html)
    /// object used by this `Bucket`.
    pub fn credentials(&self) -> &Credentials {
        &self.credentials
    }

    /// Change the credentials used by the Bucket, returning the existing
    /// credentials.
    pub fn set_credentials(&mut self, credentials: Credentials) -> Credentials {
        mem::replace(&mut self.credentials, credentials)
    }

    /// Add an extra header to send with requests to S3.
    ///
    /// Add an extra header to send with requests. Note that the library
    /// already sets a number of headers - headers set with this method will be
    /// overridden by the library headers:
    ///   * Host
    ///   * Content-Type
    ///   * Date
    ///   * Content-Length
    ///   * Authorization
    ///   * X-Amz-Content-Sha256
    ///   * X-Amz-Date
    pub fn add_header(&mut self, key: &str, value: &str) {
        self.extra_headers.insert(key.into(), value.into());
    }

    /// Get a reference to the extra headers to be passed to the S3 API.
    pub fn extra_headers(&self) -> &Headers {
        &self.extra_headers
    }

    /// Get a mutable reference to the extra headers to be passed to the S3
    /// API.
    pub fn extra_headers_mut(&mut self) -> &mut Headers {
        &mut self.extra_headers
    }

    /// Add an extra query pair to the URL used for S3 API access.
    pub fn add_query(&mut self, key: &str, value: &str) {
        self.extra_query.insert(key.into(), value.into());
    }

    /// Get a reference to the extra query pairs to be passed to the S3 API.
    pub fn extra_query(&self) -> &Query {
        &self.extra_query
    }

    /// Get a mutable reference to the extra query pairs to be passed to the S3
    /// API.
    pub fn extra_query_mut(&mut self) -> &mut Query {
        &mut self.extra_query
    }
}
<commit_msg>Simplified the OK condition.<commit_after>use std::collections::HashMap;
use std::mem;
use serde_xml;

use credentials::Credentials;
use command::Command;
use region::Region;
use request::{Request, Headers, Query};
use serde_types::{ListBucketResult, AwsError};

use error::{S3Result, ErrorKind};

/// # Example
/// ```
/// use s3::bucket::Bucket;
/// use s3::credentials::Credentials;
///
/// let bucket_name = "rust-s3-test";
/// let region = "us-east-1".parse().unwrap();
/// let credentials = Credentials::new("access_key", "secret_key", None);
///
/// let bucket = Bucket::new(bucket_name, region, credentials);
/// ```
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Bucket {
    pub name: String,
    pub region: Region,
    pub credentials: Credentials,
    pub extra_headers: Headers,
    pub extra_query: Query,
}

impl Bucket {
    /// Instantiate a new `Bucket`.
    ///
    /// # Example
    /// ```
    /// use s3::bucket::Bucket;
    /// use s3::credentials::Credentials;
    ///
    /// let bucket_name = "rust-s3-test";
    /// let region = "us-east-1".parse().unwrap();
    /// let credentials = Credentials::new("access_key", "secret_key", None);
    ///
    /// let bucket = Bucket::new(bucket_name, region, credentials);
    /// ```
    pub fn new(name: &str, region: Region, credentials: Credentials) -> Bucket {
        Bucket {
            name: name.into(),
            region: region,
            credentials: credentials,
            extra_headers: HashMap::new(),
            extra_query: HashMap::new(),
        }
    }

    /// Gets file from an S3 path.
    ///
    /// # Example:
    ///
    /// ```rust,no_run
    /// use s3::bucket::Bucket;
    /// use s3::credentials::Credentials;
    ///
    /// let bucket_name = "rust-s3-test";
    /// let region = "us-east-1".parse().unwrap();
    /// let credentials = Credentials::new("access_key", "secret_key", None);
    /// let bucket = Bucket::new(bucket_name, region, credentials);
    ///
    /// let (data, code) = bucket.get("/test.file").unwrap();
    /// println!("Code: {}\nData: {:?}", code, data);
    /// ```
    pub fn get(&self, path: &str) -> S3Result<(Vec<u8>, u32)> {
        let command = Command::Get;
        let request = Request::new(self, path, command);
        request.execute()
    }

    /// Delete file from an S3 path.
    ///
    /// # Example:
    ///
    /// ```rust,no_run
    /// use s3::bucket::Bucket;
    /// use s3::credentials::Credentials;
    ///
    /// let bucket_name = &"rust-s3-test";
    /// let region = "us-east-1".parse().unwrap();
    /// let credentials = Credentials::new("access_key", "secret_key", None);
    /// let bucket = Bucket::new(bucket_name, region, credentials);
    ///
    /// let (_, code) = bucket.delete("/test.file").unwrap();
    /// assert_eq!(204, code);
    /// ```
    pub fn delete(&self, path: &str) -> S3Result<(Vec<u8>, u32)> {
        let command = Command::Delete;
        let request = Request::new(self, path, command);
        request.execute()
    }

    /// Put into an S3 bucket.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use s3::bucket::Bucket;
    /// use s3::credentials::Credentials;
    ///
    /// let bucket_name = &"rust-s3-test";
    /// let aws_access = &"access_key";
    /// let aws_secret = &"secret_key";
    ///
    /// let bucket_name = &"rust-s3-test";
    /// let region = "us-east-1".parse().unwrap();
    /// let credentials = Credentials::new("access_key", "secret_key", None);
    /// let bucket = Bucket::new(bucket_name, region, credentials);
    ///
    /// let content = "I want to go to S3".as_bytes();
    /// let (_, code) = bucket.put("/test.file", content, "text/plain").unwrap();
    /// assert_eq!(201, code);
    /// ```
    pub fn put(&self, path: &str, data: &[u8], content_type: &str) -> S3Result<(Vec<u8>, u32)> {
        let command = Command::Put {
            content: data,
            content_type: content_type,
        };
        let request = Request::new(self, path, command);
        request.execute()
    }

    /// List the contents of an S3 bucket.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use std::str;
    /// use s3::bucket::Bucket;
    /// use s3::credentials::Credentials;
    /// let bucket_name = &"rust-s3-test";
    /// let aws_access = &"access_key";
    /// let aws_secret = &"secret_key";
    ///
    /// let bucket_name = &"rust-s3-test";
    /// let region = "us-east-1".parse().unwrap();
    /// let credentials = Credentials::new("access_key", "secret_key", None);
    /// let bucket = Bucket::new(bucket_name, region, credentials);
    ///
    /// let (list, code) = bucket.list("/", Some("/")).unwrap();
    /// assert_eq!(200, code);
    /// println!("{:?}", list);
    /// ```
    pub fn list(&self, prefix: &str, delimiter: Option<&str>) -> S3Result<(ListBucketResult, u32)> {
        let command = Command::List {
            prefix: prefix,
            delimiter: delimiter,
        };
        let request = Request::new(self, "/", command);
        let result = request.execute()?;
        let result_string = String::from_utf8_lossy(&result.0);
        if result.1 < 300 {
            let deserialized: ListBucketResult = serde_xml::deserialize(result_string.as_bytes())?;
            Ok((deserialized, result.1))
        } else {
            let deserialized: AwsError = serde_xml::deserialize(result_string.as_bytes())?;
            let err = ErrorKind::AwsError{
                info: deserialized,
                status: result.1,
                body: result_string.into_owned() };
            Err(err.into())
        }
    }

    /// Get a reference to the name of the S3 bucket.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Get a reference to the hostname of the S3 API endpoint.
    pub fn host(&self) -> &str {
        self.region.endpoint()
    }

    /// Get the region this object will connect to.
    pub fn region(&self) -> Region {
        self.region
    }

    /// Get a reference to the AWS access key.
    pub fn access_key(&self) -> &str {
        &self.credentials.access_key
    }

    /// Get a reference to the AWS secret key.
    pub fn secret_key(&self) -> &str {
        &self.credentials.secret_key
    }

    /// Get a reference to the AWS token.
    pub fn token(&self) -> Option<&str> {
        self.credentials.token.as_ref().map(|s| s.as_str())
    }

    /// Get a reference to the full [`Credentials`](struct.Credentials.html)
    /// object used by this `Bucket`.
    pub fn credentials(&self) -> &Credentials {
        &self.credentials
    }

    /// Change the credentials used by the Bucket, returning the existing
    /// credentials.
    pub fn set_credentials(&mut self, credentials: Credentials) -> Credentials {
        mem::replace(&mut self.credentials, credentials)
    }

    /// Add an extra header to send with requests to S3.
    ///
    /// Add an extra header to send with requests. Note that the library
    /// already sets a number of headers - headers set with this method will be
    /// overridden by the library headers:
    ///   * Host
    ///   * Content-Type
    ///   * Date
    ///   * Content-Length
    ///   * Authorization
    ///   * X-Amz-Content-Sha256
    ///   * X-Amz-Date
    pub fn add_header(&mut self, key: &str, value: &str) {
        self.extra_headers.insert(key.into(), value.into());
    }

    /// Get a reference to the extra headers to be passed to the S3 API.
    pub fn extra_headers(&self) -> &Headers {
        &self.extra_headers
    }

    /// Get a mutable reference to the extra headers to be passed to the S3
    /// API.
    pub fn extra_headers_mut(&mut self) -> &mut Headers {
        &mut self.extra_headers
    }

    /// Add an extra query pair to the URL used for S3 API access.
    pub fn add_query(&mut self, key: &str, value: &str) {
        self.extra_query.insert(key.into(), value.into());
    }

    /// Get a reference to the extra query pairs to be passed to the S3 API.
    pub fn extra_query(&self) -> &Query {
        &self.extra_query
    }

    /// Get a mutable reference to the extra query pairs to be passed to the S3
    /// API.
    pub fn extra_query_mut(&mut self) -> &mut Query {
        &mut self.extra_query
    }
}
<|endoftext|>