<commit_before>extern crate cairo;

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

use media::{AudioBuffer, AudioBufferIter, SAMPLES_NORM};

pub const BACKGROUND_COLOR: (f64, f64, f64) = (0.2f64, 0.2235f64, 0.2314f64);

// initial image dimensions
// will dynamically adapt if needed
const INIT_WIDTH: i32 = 2000;
const INIT_HEIGHT: i32 = 450;

pub struct WaveformImage {
    is_ready: bool,
    exposed_image: Option<cairo::ImageSurface>,
    working_image: Option<cairo::ImageSurface>,

    req_width: i32,
    req_height: i32,
    req_step_duration: u64,
    force_redraw: bool,

    pub first_sample: usize,
    pub last_sample: usize,
    pub sample_window: usize,
    pub sample_step: usize,
    pub sample_step_f: f64,
}

impl WaveformImage {
    pub fn new() -> Self {
        WaveformImage {
            is_ready: false,
            exposed_image: Some(
                cairo::ImageSurface::create(
                    cairo::Format::Rgb24, INIT_WIDTH, INIT_HEIGHT
                ).unwrap()
            ),
            working_image: Some(
                cairo::ImageSurface::create(
                    cairo::Format::Rgb24, INIT_WIDTH, INIT_HEIGHT
                ).unwrap()
            ),

            req_width: 0,
            req_height: 0,
            req_step_duration: 0,
            force_redraw: false,

            first_sample: 0,
            last_sample: 0,
            sample_window: 0,
            sample_step: 0,
            sample_step_f: 0f64,
        }
    }

    pub fn cleanup(&mut self) {
        // clear for reuse
        self.is_ready = false;

        // self.exposed_image & self.working_image
        // will be cleaned on next with draw

        self.req_width = 0;
        self.req_height = 0;
        self.req_step_duration = 0;
        self.force_redraw = false;

        self.first_sample = 0;
        self.last_sample = 0;
        self.sample_window = 0;
        self.sample_step = 0;
        self.sample_step_f = 0f64;
    }

    pub fn update_dimensions(&mut self,
        duration: u64,
        width: i32,
        height: i32
    ) -> bool {
        // if the requested height is different from current height
        // it might be necessary to force rendering when stream
        // is paused or eos

        let width_f = width as u64;
        let req_step_duration =
            if duration > width_f {
                duration / width_f
            } else {
                1
            };

        self.force_redraw =
            self.req_height != height
            || self.req_width != width
            || self.req_step_duration != req_step_duration;

        self.req_width = width;
        self.req_height = height;
        self.req_step_duration = req_step_duration;

        self.is_ready
    }

    pub fn is_ready(&self) -> bool {
        self.is_ready
    }

    pub fn get_image(&self) -> &cairo::ImageSurface {
        self.exposed_image.as_ref().unwrap()
    }

    pub fn update_from_other(&mut self, other: &WaveformImage) {
        self.sample_step = other.sample_step;
        self.sample_step_f = other.sample_step_f;
        self.req_step_duration = other.req_step_duration;
        self.req_width = other.req_width;
        self.req_height = other.req_height;
        self.force_redraw = other.force_redraw;
    }

    // Render the waveform within the provided limits.
    // This function is called from a working buffer
    // which means that self.exposed_image is the image
    // that was previously exposed to the UI.
    // This also means that we can safely deal with both
    // images since none of them is exposed at this very moment.
    // The rendering process reuses the previously rendered image
    // whenever possible.
    pub fn render(&mut self,
        audio_buffer: &AudioBuffer,
        first_sample: usize,
        last_sample: usize,
        sample_duration: f64,
    ) {
        #[cfg(feature = "profiling-waveform-image")]
        let start = Utc::now();

        // use an integer number of samples per step
        let sample_step_f = self.req_step_duration as f64 / sample_duration;
        let sample_step = (sample_step_f as usize).max(1);

        if audio_buffer.samples.len() < sample_step {
            // buffer too small to render
            return;
        }

        // Align requested first and last samples in order to keep a steady
        // offset between redraws. This allows using the same samples
        // for a given req_step_duration and avoiding flickering
        // between redraws
        let mut first_sample =
            first_sample / sample_step * sample_step;
        if first_sample < audio_buffer.first_sample {
            // first sample might be smaller than audio_buffer.first_sample
            // due to alignement on sample_step
            first_sample += sample_step;
        }

        let last_sample = last_sample / sample_step * sample_step;

        let extraction_samples_window = (last_sample - first_sample) / sample_step;

        let mut must_redraw = !self.is_ready || self.force_redraw;
        if !must_redraw && first_sample >= self.first_sample
        && last_sample <= self.last_sample
        {   // traget extraction fits in previous extraction
            return;
        } else if first_sample + extraction_samples_window < self.first_sample
            || first_sample > self.last_sample
        {   // current samples extraction doesn't overlap with samples in previous image
            must_redraw = true;
        }

        let mut y_offset = 0f64;
        let (working_image, previous_image) = {
            let mut can_reuse = false;
            let target_width = (extraction_samples_window as i32).max(self.req_width);

            let working_image = self.working_image.take().unwrap();
            if self.req_height != working_image.get_height() {
                // height has changed => scale samples amplitude accordingly
                must_redraw = true;
                y_offset = (self.req_height - working_image.get_height()) as f64;
            }

            if target_width <= working_image.get_width()
            && self.req_height <= working_image.get_height() {
                // expected dimensions fit in current working image => reuse it
                can_reuse = true;
            }

            if can_reuse {
                (
                    working_image,
                    self.exposed_image.take().unwrap(),
                )
            } else {
                // can't reuse => create new images and force redraw
                must_redraw = true;

                (
                    cairo::ImageSurface::create( // working_image
                        cairo::Format::Rgb24,
                        target_width,
                        self.req_height
                    ).expect(
                        &format!(
                            "WaveformBuffer.render: couldn't create image surface with width {}",
                            target_width,
                        )
                    ),
                    cairo::ImageSurface::create( // will be used as previous_image
                        cairo::Format::Rgb24,
                        target_width,
                        self.req_height
                    ).unwrap() // working_image could be created with same dimensions
                )
            }
        };

        let cr = cairo::Context::new(&working_image);

        if must_redraw {
            // Initialization or resolution has changed or seek requested
            // redraw the whole range
            self.redraw(&cr,
                audio_buffer.iter(first_sample, last_sample, sample_step),
            );

            self.sample_step = sample_step;
            self.sample_step_f = sample_step_f;
            self.first_sample = first_sample;
            self.last_sample = last_sample;
        } else {
            // can reuse previous context
            // Note: condition first_sample >= self.self.first_sample
            //                 && last_sample <= self.self.last_sample
            // (traget extraction fits in previous extraction)
            // already checked

            if first_sample < self.first_sample {
                // append samples before previous first sample
                let image_width_as_samples =
                    working_image.get_width() as usize * sample_step;

                self.last_sample = self.last_sample.min(
                    first_sample + image_width_as_samples
                );

                // shift previous image to the right
                let x_offset = (
                    (self.first_sample - first_sample) / sample_step
                ) as f64;

                println!(
                    "WaveformImage: appending {} samples before previous first sample",
                    self.first_sample - first_sample
                );

                let prev_first_sample = self.first_sample;
                self.append_left(&cr,
                    &previous_image,
                    x_offset,
                    y_offset,
                    audio_buffer.iter(first_sample, prev_first_sample, sample_step),
                );

                self.first_sample = first_sample;
            } else {
                // first_sample >= self.first_sample
                // Note: due to previous conditions tested before,
                // this also implies:
                assert!(last_sample > self.last_sample);

                // shift previous image to the left (if necessary)
                let x_offset = -((
                    (first_sample - self.first_sample) / sample_step
                ) as f64);

                // append samples after previous last sample
                let first_sample_to_draw = self.last_sample.max(first_sample);
                let first_x_to_draw = (
                    (first_sample_to_draw - self.first_sample) / sample_step
                ) as f64 + x_offset;

                self.append_right(&cr,
                    &previous_image,
                    x_offset,
                    y_offset,
                    audio_buffer.iter(first_sample_to_draw, last_sample, sample_step),
                    first_x_to_draw,
                    working_image.get_width() // clear_limit
                );

                self.first_sample = first_sample;
                self.last_sample = last_sample;
            }
        }

        // swap images
        self.working_image = Some(previous_image);
        self.exposed_image = Some(working_image);

        self.sample_window = self.last_sample - self.first_sample;
        self.is_ready = true;
        self.force_redraw = false;

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

        #[cfg(feature = "profiling-waveform-image")]
        println!("waveform-image,{},{}",
            start.time().format("%H:%M:%S%.6f"),
            end.time().format("%H:%M:%S%.6f"),
        );
    }

    // redraw the whole sample range on a clean image
    fn redraw(&mut self,
        cr: &cairo::Context,
        sample_iter: AudioBufferIter
    ) {
        cr.set_source_rgb(
            BACKGROUND_COLOR.0,
            BACKGROUND_COLOR.1,
            BACKGROUND_COLOR.2
        );
        cr.paint();

        self.set_scale(&cr);
        self.draw_samples(cr, sample_iter, 0f64);
    }

    fn append_left(&mut self,
        cr: &cairo::Context,
        previous_image: &cairo::ImageSurface,
        x_offset: f64,
        y_offset: f64,
        sample_iter: AudioBufferIter,
    ) {
        cr.set_source_surface(previous_image, x_offset, y_offset);
        cr.paint();

        self.set_scale(&cr);
        self.clear_area(&cr, 0f64, x_offset);
        self.draw_samples(cr, sample_iter, 0f64);
    }

    fn append_right(&mut self,
        cr: &cairo::Context,
        previous_image: &cairo::ImageSurface,
        x_offset: f64,
        y_offset: f64,
        sample_iter: AudioBufferIter,
        first_x: f64,
        clear_limit: i32,
    ) {
        cr.set_source_surface(previous_image, x_offset, y_offset);
        cr.paint();

        self.set_scale(&cr);
        self.clear_area(&cr, first_x, f64::from(clear_limit));
        self.draw_samples(cr, sample_iter, first_x);
    }

    fn set_scale(&self, cr: &cairo::Context) {
        cr.scale(1f64, f64::from(self.req_height) / SAMPLES_NORM);
    }

    fn draw_samples(&self,
        cr: &cairo::Context,
        mut sample_iter: AudioBufferIter,
        first_x: f64,
    ) {
        if sample_iter.size_hint().0 > 0 {
            // Stroke selected samples
            cr.set_line_width(0.5f64);
            cr.set_source_rgb(0.8f64, 0.8f64, 0.8f64);

            let mut x = first_x;
            let mut sample_value = *sample_iter.next().unwrap();
            for sample in sample_iter {
                cr.move_to(x, sample_value);
                x += 1f64;
                sample_value = *sample;
                cr.line_to(x, sample_value);
                cr.stroke();
            }
        }
    }

    // clear samples previously rendered
    fn clear_area(&self, cr: &cairo::Context, first_x: f64, limit_x: f64) {
        cr.set_source_rgb(
            BACKGROUND_COLOR.0,
            BACKGROUND_COLOR.1,
            BACKGROUND_COLOR.2
        );
        cr.rectangle(first_x, 0f64, limit_x - first_x, SAMPLES_NORM);
        cr.fill();
    }
}
<commit_msg>WaveformImage: allow inserting samples on the left and on the right at once (#34) And introduce unit tests<commit_after>extern crate cairo;

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

use media::{AudioBuffer, AudioBufferIter, SAMPLES_NORM};

pub const BACKGROUND_COLOR: (f64, f64, f64) = (0.2f64, 0.2235f64, 0.2314f64);

// initial image dimensions
// will dynamically adapt if needed
const INIT_WIDTH: i32 = 2000;
const INIT_HEIGHT: i32 = 450;

pub struct WaveformImage {
    is_ready: bool,
    exposed_image: Option<cairo::ImageSurface>,
    working_image: Option<cairo::ImageSurface>,

    req_width: i32,
    req_height: i32,
    req_step_duration: u64,
    force_redraw: bool,

    pub first_sample: usize,
    pub last_sample: usize,
    pub sample_window: usize,
    pub sample_step: usize,
    pub sample_step_f: f64,
}

impl WaveformImage {
    pub fn new() -> Self {
        WaveformImage {
            is_ready: false,
            exposed_image: Some(
                cairo::ImageSurface::create(
                    cairo::Format::Rgb24, INIT_WIDTH, INIT_HEIGHT
                ).unwrap()
            ),
            working_image: Some(
                cairo::ImageSurface::create(
                    cairo::Format::Rgb24, INIT_WIDTH, INIT_HEIGHT
                ).unwrap()
            ),

            req_width: 0,
            req_height: 0,
            req_step_duration: 0,
            force_redraw: false,

            first_sample: 0,
            last_sample: 0,
            sample_window: 0,
            sample_step: 0,
            sample_step_f: 0f64,
        }
    }

    pub fn cleanup(&mut self) {
        // clear for reuse
        self.is_ready = false;

        // self.exposed_image & self.working_image
        // will be cleaned on next with draw

        self.req_width = 0;
        self.req_height = 0;
        self.req_step_duration = 0;
        self.force_redraw = false;

        self.first_sample = 0;
        self.last_sample = 0;
        self.sample_window = 0;
        self.sample_step = 0;
        self.sample_step_f = 0f64;
    }

    pub fn update_dimensions(&mut self,
        duration: u64,
        width: i32,
        height: i32
    ) -> bool {
        // if the requested height is different from current height
        // it might be necessary to force rendering when stream
        // is paused or eos

        let width_f = width as u64;
        let req_step_duration =
            if duration > width_f {
                duration / width_f
            } else {
                1
            };

        self.force_redraw =
            self.req_height != height
            || self.req_width != width
            || self.req_step_duration != req_step_duration;

        self.req_width = width;
        self.req_height = height;
        self.req_step_duration = req_step_duration;

        self.is_ready
    }

    pub fn is_ready(&self) -> bool {
        self.is_ready
    }

    pub fn get_image(&self) -> &cairo::ImageSurface {
        self.exposed_image.as_ref().unwrap()
    }

    pub fn update_from_other(&mut self, other: &WaveformImage) {
        self.sample_step = other.sample_step;
        self.sample_step_f = other.sample_step_f;
        self.req_step_duration = other.req_step_duration;
        self.req_width = other.req_width;
        self.req_height = other.req_height;
        self.force_redraw = other.force_redraw;
    }

    // Render the waveform within the provided limits.
    // This function is called from a working buffer
    // which means that self.exposed_image is the image
    // that was previously exposed to the UI.
    // This also means that we can safely deal with both
    // images since none of them is exposed at this very moment.
    // The rendering process reuses the previously rendered image
    // whenever possible.
    pub fn render(&mut self,
        audio_buffer: &AudioBuffer,
        first_sample: usize,
        last_sample: usize,
        sample_duration: f64,
    ) {
        #[cfg(feature = "profiling-waveform-image")]
        let start = Utc::now();

        // use an integer number of samples per step
        let sample_step_f = self.req_step_duration as f64 / sample_duration;
        let sample_step = (sample_step_f as usize).max(1);

        if audio_buffer.samples.len() < sample_step {
            // buffer too small to render
            return;
        }

        // Align requested first and last samples in order to keep a steady
        // offset between redraws. This allows using the same samples
        // for a given req_step_duration and avoiding flickering
        // between redraws
        let mut first_sample =
            first_sample / sample_step * sample_step;
        if first_sample < audio_buffer.first_sample {
            // first sample might be smaller than audio_buffer.first_sample
            // due to alignement on sample_step
            first_sample += sample_step;
        }

        let last_sample = last_sample / sample_step * sample_step;

        let extraction_samples_window = (last_sample - first_sample) / sample_step;

        let mut must_redraw = !self.is_ready || self.force_redraw;
        if !must_redraw && first_sample >= self.first_sample
        && last_sample <= self.last_sample
        {   // traget extraction fits in previous extraction
            return;
        } else if first_sample + extraction_samples_window < self.first_sample
            || first_sample > self.last_sample
        {   // current samples extraction doesn't overlap with samples in previous image
            must_redraw = true;
        }

        let mut y_offset = 0f64;
        let (working_image, previous_image) = {
            let mut can_reuse = false;
            let target_width = (extraction_samples_window as i32).max(self.req_width);

            let working_image = self.working_image.take().unwrap();
            if self.req_height != working_image.get_height() {
                // height has changed => scale samples amplitude accordingly
                must_redraw = true;
                y_offset = (self.req_height - working_image.get_height()) as f64;
            }

            if target_width <= working_image.get_width()
            && self.req_height <= working_image.get_height() {
                // expected dimensions fit in current working image => reuse it
                can_reuse = true;
            }

            if can_reuse {
                (
                    working_image,
                    self.exposed_image.take().unwrap(),
                )
            } else {
                // can't reuse => create new images and force redraw
                must_redraw = true;

                (
                    cairo::ImageSurface::create( // working_image
                        cairo::Format::Rgb24,
                        target_width,
                        self.req_height
                    ).expect(
                        &format!(
                            "WaveformBuffer.render: couldn't create image surface with width {}",
                            target_width,
                        )
                    ),
                    cairo::ImageSurface::create( // will be used as previous_image
                        cairo::Format::Rgb24,
                        target_width,
                        self.req_height
                    ).unwrap() // working_image could be created with same dimensions
                )
            }
        };

        let cr = cairo::Context::new(&working_image);

        if must_redraw {
            // Initialization or resolution has changed or seek requested
            // redraw the whole range
            self.redraw(&cr,
                audio_buffer.iter(first_sample, last_sample, sample_step),
            );

            self.sample_step = sample_step;
            self.sample_step_f = sample_step_f;
            self.first_sample = first_sample;
            self.last_sample = last_sample;
        } else {
            // can reuse previous context
            // Note: condition first_sample >= self.self.first_sample
            //                 && last_sample <= self.self.last_sample
            // (traget extraction fits in previous extraction)
            // already checked

            if first_sample < self.first_sample {
                // append samples before previous first sample
                let sample_offset = self.first_sample - first_sample;
                let x_offset = sample_offset as f64 / self.sample_step_f;

                println!(
                    "WaveformImage: appending {} samples before previous first sample",
                    self.first_sample - first_sample
                );

                let prev_first_sample = self.first_sample;
                self.append_left(&cr,
                    &previous_image,
                    x_offset,
                    y_offset,
                    audio_buffer.iter(first_sample, prev_first_sample, sample_step),
                );

                self.first_sample = first_sample;
                self.last_sample = self.last_sample + sample_offset;
            }

            if last_sample > self.last_sample {
                // shift previous image to the left (if necessary)
                // and append missing samples to the right
                let x_offset = -((
                    (first_sample - self.first_sample) / self.sample_step
                ) as f64);

                // append samples after previous last sample
                let first_sample_to_draw = self.last_sample.max(first_sample);
                let first_x_to_draw = (
                    (first_sample_to_draw - self.first_sample) / sample_step
                ) as f64 + x_offset;

                self.append_right(&cr,
                    &previous_image,
                    x_offset,
                    y_offset,
                    audio_buffer.iter(first_sample_to_draw, last_sample, sample_step),
                    first_x_to_draw,
                    working_image.get_width() // clear_limit
                );

                self.first_sample = first_sample;
                self.last_sample = last_sample;
            }
        }

        // swap images
        self.working_image = Some(previous_image);
        self.exposed_image = Some(working_image);

        self.sample_window = self.last_sample - self.first_sample;
        self.is_ready = true;
        self.force_redraw = false;

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

        #[cfg(feature = "profiling-waveform-image")]
        println!("waveform-image,{},{}",
            start.time().format("%H:%M:%S%.6f"),
            end.time().format("%H:%M:%S%.6f"),
        );
    }

    // redraw the whole sample range on a clean image
    fn redraw(&mut self,
        cr: &cairo::Context,
        sample_iter: AudioBufferIter
    ) {
        cr.set_source_rgb(
            BACKGROUND_COLOR.0,
            BACKGROUND_COLOR.1,
            BACKGROUND_COLOR.2
        );
        cr.paint();

        self.set_scale(&cr);
        self.draw_samples(cr, sample_iter, 0f64);
    }

    fn append_left(&mut self,
        cr: &cairo::Context,
        previous_image: &cairo::ImageSurface,
        x_offset: f64,
        y_offset: f64,
        sample_iter: AudioBufferIter,
    ) {
        cr.set_source_surface(previous_image, x_offset, y_offset);
        cr.paint();

        self.set_scale(&cr);
        self.clear_area(&cr, 0f64, x_offset);
        self.draw_samples(cr, sample_iter, 0f64);
    }

    fn append_right(&mut self,
        cr: &cairo::Context,
        previous_image: &cairo::ImageSurface,
        x_offset: f64,
        y_offset: f64,
        sample_iter: AudioBufferIter,
        first_x: f64,
        clear_limit: i32,
    ) {
        cr.set_source_surface(previous_image, x_offset, y_offset);
        cr.paint();

        self.set_scale(&cr);
        self.clear_area(&cr, first_x, f64::from(clear_limit));
        self.draw_samples(cr, sample_iter, first_x);
    }

    fn set_scale(&self, cr: &cairo::Context) {
        cr.scale(1f64, f64::from(self.req_height) / SAMPLES_NORM);
    }

    fn draw_samples(&self,
        cr: &cairo::Context,
        mut sample_iter: AudioBufferIter,
        first_x: f64,
    ) {
        if sample_iter.size_hint().0 > 0 {
            // Stroke selected samples
            cr.set_line_width(0.5f64);
            cr.set_source_rgb(0.8f64, 0.8f64, 0.8f64);

            let mut x = first_x;
            let mut sample_value = *sample_iter.next().unwrap();
            for sample in sample_iter {
                cr.move_to(x, sample_value);
                x += 1f64;
                sample_value = *sample;
                cr.line_to(x, sample_value);
                cr.stroke();
            }
        }
    }

    // clear samples previously rendered
    fn clear_area(&self, cr: &cairo::Context, first_x: f64, limit_x: f64) {
        cr.set_source_rgb(
            BACKGROUND_COLOR.0,
            BACKGROUND_COLOR.1,
            BACKGROUND_COLOR.2
        );
        cr.rectangle(first_x, 0f64, limit_x - first_x, SAMPLES_NORM);
        cr.fill();
    }
}

#[cfg(test)]
mod tests {
    extern crate cairo;

    extern crate gstreamer as gst;
    extern crate gstreamer_audio as gst_audio;

    use std::fs::{create_dir, File};
    use std::io::ErrorKind;

    use std::{i16, u16};

    use media::AudioBuffer;
    use ui::WaveformImage;

    const OUT_DIR: &'static str = "target/test";
    const SAMPLE_RATE: i32 = 300;

    fn prepare_tests() {
        match create_dir(&OUT_DIR) {
            Ok(_) => (),
            Err(error) => match error.kind() {
                ErrorKind::AlreadyExists => (),
                _ =>
                    panic!("WaveformImage test: couldn't create directory {}",
                        OUT_DIR
                    ),
            },
        }
    }

    #[test]
    fn multiple_renderings() {
        gst::init().unwrap();

        prepare_tests();

        // AudioBuffer
        let mut audio_buffer = AudioBuffer::new(1_000_000_000); // 1s
        let caps = gst::Caps::new_simple(
            "audio/x-raw",
            &[
                ("format", &gst_audio::AUDIO_FORMAT_S16.to_string()),
                ("layout", &"interleaved"),
                ("channels", &1),
                ("rate", &SAMPLE_RATE),
            ],
        );
        audio_buffer.set_caps(&caps);

        // WaveformImage
        let mut waveform = WaveformImage::new();
        waveform.update_dimensions(
            1_000_000_000, // 1s
            SAMPLE_RATE,
            SAMPLE_RATE
        );

        // Compute a buffer in the specified range
        // which will be rendered as a diagonal on the Waveform image
        // from left top corner to right bottom of the target image
        // if all samples are rendered in the range [0:SAMPLE_RATE]
        fn build_buffer(first_sample: usize, last_sample: usize) -> Vec<i16> {
            let mut buffer: Vec<i16> = Vec::new();
            let mut index = first_sample;
            while index < last_sample {
                buffer.push((
                    i16::MAX as i32
                    - (index as f64 / SAMPLE_RATE as f64 * u16::MAX as f64
                    ) as i32
                ) as i16);
                index += 1;
            }
            buffer
        }

        fn render_with_samples(
            waveform: &mut WaveformImage,
            audio_buffer: &mut AudioBuffer,
            caps: &gst::Caps,
            first: usize,
            last: usize,
            segement_first_sample: usize,
        ) {
            audio_buffer.push_samples(
                &build_buffer(first, last),
                first,
                segement_first_sample,
                &caps
            );
            let first_sample_to_extract =
                if audio_buffer.last_sample
                    > audio_buffer.first_sample + SAMPLE_RATE as usize
                {
                    audio_buffer.last_sample - SAMPLE_RATE as usize
                } else {
                    audio_buffer.first_sample
                };

            waveform.render(&audio_buffer,
                first_sample_to_extract,
                audio_buffer.last_sample,
                audio_buffer.sample_duration,
            );

            let image = waveform.get_image();

            let mut output_file = File::create(
                    format!(
                        "{}/waveform_image_{:03}_{:03}.png", OUT_DIR,
                        waveform.first_sample,
                        waveform.last_sample
                    )
                ).expect("WaveformImage test: couldn't create output file");
            image.write_to_png(&mut output_file)
                .expect("WaveformImage test: couldn't write waveform image");
        }

        render_with_samples(&mut waveform, &mut audio_buffer, &caps, 100, 200, 100);
        // overlap on the left
        render_with_samples(&mut waveform, &mut audio_buffer, &caps,  50, 150, 50);
        render_with_samples(&mut waveform, &mut audio_buffer, &caps,   0, 100, 0);
        // appended to the right
        render_with_samples(&mut waveform, &mut audio_buffer, &caps, 200, 300, 200);

        // scrolling and overlaping on the right
        render_with_samples(&mut waveform, &mut audio_buffer, &caps, 250, 350, 250);
    }
}
<|endoftext|>


<commit_before>#![allow(dead_code)]
use raft::errors::{Result, Error};
use raft::Storage;
use protobuf::{self, RepeatedField};
use proto::raftpb::{HardState, Entry, EntryType, Message, Snapshot, MessageType, ConfChange,
                    ConfChangeType, ConfState};
use raft::raft::{Config, Raft, SoftState, INVALID_ID};
use raft::Status;
use std::sync::Arc;

#[derive(Debug, Default)]
pub struct Peer {
    pub id: u64,
    pub context: Option<Vec<u8>>,
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum SnapshotStatus {
    Finish,
    Failure,
}

fn is_local_msg(m: &Message) -> bool {
    match m.get_msg_type() {
        MessageType::MsgHup |
        MessageType::MsgBeat |
        MessageType::MsgUnreachable |
        MessageType::MsgSnapStatus |
        MessageType::MsgCheckQuorum => true,
        _ => false,
    }
}

fn is_response_msg(m: &Message) -> bool {
    match m.get_msg_type() {
        MessageType::MsgAppendResponse |
        MessageType::MsgRequestVoteResponse |
        MessageType::MsgHeartbeatResponse |
        MessageType::MsgUnreachable => true,
        _ => false,
    }
}

pub fn is_empty_snap(s: &Snapshot) -> bool {
    s.get_metadata().get_index() == 0
}

// Ready encapsulates the entries and messages that are ready to read,
// be saved to stable storage, committed or sent to other peers.
// All fields in Ready are read-only.
#[derive(Default, Debug, PartialEq)]
pub struct Ready {
    // The current volatile state of a Node.
    // SoftState will be nil if there is no update.
    // It is not required to consume or store SoftState.
    pub ss: Option<SoftState>,

    // The current state of a Node to be saved to stable storage BEFORE
    // Messages are sent.
    // HardState will be equal to empty state if there is no update.
    pub hs: Option<HardState>,

    // Entries specifies entries to be saved to stable storage BEFORE
    // Messages are sent.
    pub entries: Vec<Entry>,

    // Snapshot specifies the snapshot to be saved to stable storage.
    pub snapshot: Snapshot,

    // CommittedEntries specifies entries to be committed to a
    // store/state-machine. These have previously been committed to stable
    // store.
    pub committed_entries: Vec<Entry>,

    // Messages specifies outbound messages to be sent AFTER Entries are
    // committed to stable storage.
    // If it contains a MsgSnap message, the application MUST report back to raft
    // when the snapshot has been received or has failed by calling ReportSnapshot.
    pub messages: Vec<Message>,
}

impl Ready {
    fn new<T: Storage>(raft: &mut Raft<T>, prev_ss: &SoftState, prev_hs: &HardState) -> Ready {
        let mut rd = Ready {
            entries: raft.raft_log.unstable_entries().unwrap_or(&[]).to_vec(),
            committed_entries: raft.raft_log.next_entries().unwrap_or_else(Vec::new),
            messages: raft.msgs.drain(..).collect(),
            ..Default::default()
        };
        let ss = raft.soft_state();
        if &ss != prev_ss {
            rd.ss = Some(ss);
        }
        let hs = raft.hard_state();
        if &hs != prev_hs {
            rd.hs = Some(hs);
        }
        if raft.raft_log.get_unstable().snapshot.is_some() {
            rd.snapshot = raft.raft_log.get_unstable().snapshot.clone().unwrap();
        }
        rd
    }
}

// RawNode is a thread-unsafe Node.
// The methods of this struct correspond to the methods of Node and are described
// more fully there.
pub struct RawNode<T: Storage> {
    pub raft: Raft<T>,
    prev_ss: SoftState,
    prev_hs: HardState,
}

impl<T: Storage> RawNode<T> {
    // NewRawNode returns a new RawNode given configuration and a list of raft peers.
    pub fn new(config: &Config, store: Arc<T>, peers: &[Peer]) -> Result<RawNode<T>> {
        assert!(config.id != 0, "config.id must not be zero");
        let r = Raft::new(config, store);
        let mut rn = RawNode {
            raft: r,
            prev_hs: Default::default(),
            prev_ss: Default::default(),
        };
        let last_index = rn.raft.get_store().last_index().expect("");
        if last_index == 0 {
            rn.raft.become_follower(1, INVALID_ID);
            let mut ents = Vec::with_capacity(peers.len());
            for (i, peer) in peers.iter().enumerate() {
                let mut cc = ConfChange::new();
                cc.set_change_type(ConfChangeType::ConfChangeAddNode);
                cc.set_node_id(peer.id);
                if peer.context.is_some() {
                    cc.set_context(peer.context.as_ref().unwrap().clone());
                }
                let data = protobuf::Message::write_to_bytes(&cc)
                               .expect("unexpected marshal error");
                let mut e = Entry::new();
                e.set_entry_type(EntryType::EntryConfChange);
                e.set_term(1);
                e.set_index(i as u64 + 1);
                e.set_data(data);
                ents.push(e);
            }
            rn.raft.raft_log.append(&ents);
            rn.raft.raft_log.committed = ents.len() as u64;
            for peer in peers {
                rn.raft.add_node(peer.id);
            }
        }
        rn.prev_ss = rn.raft.soft_state();
        rn.prev_hs = rn.raft.hard_state();
        Ok(rn)
    }

    fn commit_ready(&mut self, rd: Ready) {
        if rd.ss.is_some() {
            self.prev_ss = rd.ss.unwrap();
        }
        if let Some(e) = rd.hs {
            if e != HardState::new() {
                self.prev_hs = e;
            }
        }
        if self.prev_hs.get_commit() != 0 {
            // In most cases, prevHardSt and rd.HardState will be the same
            // because when there are new entries to apply we just sent a
            // HardState with an updated Commit value. However, on initial
            // startup the two are different because we don't send a HardState
            // until something changes, but we do send any un-applied but
            // committed entries (and previously-committed entries may be
            // incorporated into the snapshot, even if rd.CommittedEntries is
            // empty). Therefore we mark all committed entries as applied
            // whether they were included in rd.HardState or not.
            self.raft.raft_log.applied_to(self.prev_hs.get_commit());
        }
        if !rd.entries.is_empty() {
            let e = rd.entries.last().unwrap();
            self.raft.raft_log.stable_to(e.get_index(), e.get_term());
        }
        if rd.snapshot != Snapshot::new() {
            self.raft.raft_log.stable_snap_to(rd.snapshot.get_metadata().get_index());
        }
    }

    // Tick advances the internal logical clock by a single tick.
    pub fn tick(&mut self) {
        self.raft.tick();
    }

    // Campaign causes this RawNode to transition to candidate state.
    pub fn campaign(&mut self) -> Result<()> {
        let mut m = Message::new();
        m.set_msg_type(MessageType::MsgHup);
        self.raft.step(m)
    }

    // Propose proposes data be appended to the raft log.
    pub fn propose(&mut self, data: Vec<u8>) -> Result<()> {
        let mut m = Message::new();
        m.set_msg_type(MessageType::MsgPropose);
        m.set_from(self.raft.id);
        let mut e = Entry::new();
        e.set_data(data);
        m.set_entries(RepeatedField::from_vec(vec![e]));
        self.raft.step(m)
    }

    // ProposeConfChange proposes a config change.
    pub fn propose_conf_change(&mut self, cc: ConfChange) -> Result<()> {
        let data = try!(protobuf::Message::write_to_bytes(&cc));
        let mut m = Message::new();
        m.set_msg_type(MessageType::MsgPropose);
        let mut e = Entry::new();
        e.set_entry_type(EntryType::EntryConfChange);
        e.set_data(data);
        m.set_entries(RepeatedField::from_vec(vec![e]));
        self.raft.step(m)
    }

    pub fn apply_conf_change(&mut self, cc: ConfChange) -> ConfState {
        if cc.get_node_id() == INVALID_ID {
            self.raft.reset_pending_conf();
            let mut cs = ConfState::new();
            cs.set_nodes(self.raft.nodes());
            return cs;
        }
        let nid = cc.get_node_id();
        assert!(cc.has_change_type(), "unexpected conf type");
        match cc.get_change_type() {
            ConfChangeType::ConfChangeAddNode => self.raft.add_node(nid),
            ConfChangeType::ConfChangeRemoveNode => self.raft.remove_node(nid),
            ConfChangeType::ConfChangeUpdateNode => self.raft.reset_pending_conf(),
        }
        let mut cs = ConfState::new();
        cs.set_nodes(self.raft.nodes());
        cs
    }

    // Step advances the state machine using the given message.
    pub fn step(&mut self, m: Message) -> Result<()> {
        // ignore unexpected local messages receiving over network
        if is_local_msg(&m) {
            return Err(Error::StepLocalMsg);
        }
        if self.raft.prs.contains_key(&m.get_from()) || !is_response_msg(&m) {
            return self.raft.step(m);
        }
        Err(Error::StepPeerNotFound)
    }

    // Ready returns the current point-in-time state of this RawNode.
    pub fn ready(&mut self) -> Ready {
        Ready::new(&mut self.raft, &self.prev_ss, &self.prev_hs)
    }

    // HasReady called when RawNode user need to check if any Ready pending.
    // Checking logic in this method should be consistent with Ready.containsUpdates().
    pub fn has_ready(&self) -> bool {
        let raft = &self.raft;
        if raft.soft_state() != self.prev_ss {
            return true;
        }
        let hs = raft.hard_state();
        if hs != HardState::new() && hs != self.prev_hs {
            return true;
        }
        if Some(true) == raft.raft_log.get_unstable().snapshot.as_ref().map(|s| !is_empty_snap(s)) {
            return true;
        }
        if !raft.msgs.is_empty() || raft.raft_log.unstable_entries().is_some() ||
           raft.raft_log.has_next_entries() {
            return true;
        }
        false
    }

    // Advance notifies the RawNode that the application has applied and saved progress in the
    // last Ready results.
    pub fn advance(&mut self, rd: Ready) {
        self.commit_ready(rd);
    }

    // Status returns the current status of the given group.
    pub fn status(&self) -> Status {
        Status::new(&self.raft)
    }

    // ReportUnreachable reports the given node is not reachable for the last send.
    pub fn report_unreachable(&mut self, id: u64) {
        let mut m = Message::new();
        m.set_msg_type(MessageType::MsgUnreachable);
        m.set_from(id);
        // we don't care if it is ok actually
        self.raft.step(m).is_ok();
    }

    // ReportSnapshot reports the status of the sent snapshot.
    pub fn report_snapshot(&mut self, id: u64, status: SnapshotStatus) {
        let rej = status == SnapshotStatus::Failure;
        let mut m = Message::new();
        m.set_msg_type(MessageType::MsgSnapStatus);
        m.set_from(id);
        m.set_reject(rej);
        // we don't care if it is ok actually
        self.raft.step(m).is_ok();
    }
}
<commit_msg>remove ConfChange prefix<commit_after>#![allow(dead_code)]
use raft::errors::{Result, Error};
use raft::Storage;
use protobuf::{self, RepeatedField};
use proto::raftpb::{HardState, Entry, EntryType, Message, Snapshot, MessageType, ConfChange,
                    ConfChangeType, ConfState};
use raft::raft::{Config, Raft, SoftState, INVALID_ID};
use raft::Status;
use std::sync::Arc;

#[derive(Debug, Default)]
pub struct Peer {
    pub id: u64,
    pub context: Option<Vec<u8>>,
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum SnapshotStatus {
    Finish,
    Failure,
}

fn is_local_msg(m: &Message) -> bool {
    match m.get_msg_type() {
        MessageType::MsgHup |
        MessageType::MsgBeat |
        MessageType::MsgUnreachable |
        MessageType::MsgSnapStatus |
        MessageType::MsgCheckQuorum => true,
        _ => false,
    }
}

fn is_response_msg(m: &Message) -> bool {
    match m.get_msg_type() {
        MessageType::MsgAppendResponse |
        MessageType::MsgRequestVoteResponse |
        MessageType::MsgHeartbeatResponse |
        MessageType::MsgUnreachable => true,
        _ => false,
    }
}

pub fn is_empty_snap(s: &Snapshot) -> bool {
    s.get_metadata().get_index() == 0
}

// Ready encapsulates the entries and messages that are ready to read,
// be saved to stable storage, committed or sent to other peers.
// All fields in Ready are read-only.
#[derive(Default, Debug, PartialEq)]
pub struct Ready {
    // The current volatile state of a Node.
    // SoftState will be nil if there is no update.
    // It is not required to consume or store SoftState.
    pub ss: Option<SoftState>,

    // The current state of a Node to be saved to stable storage BEFORE
    // Messages are sent.
    // HardState will be equal to empty state if there is no update.
    pub hs: Option<HardState>,

    // Entries specifies entries to be saved to stable storage BEFORE
    // Messages are sent.
    pub entries: Vec<Entry>,

    // Snapshot specifies the snapshot to be saved to stable storage.
    pub snapshot: Snapshot,

    // CommittedEntries specifies entries to be committed to a
    // store/state-machine. These have previously been committed to stable
    // store.
    pub committed_entries: Vec<Entry>,

    // Messages specifies outbound messages to be sent AFTER Entries are
    // committed to stable storage.
    // If it contains a MsgSnap message, the application MUST report back to raft
    // when the snapshot has been received or has failed by calling ReportSnapshot.
    pub messages: Vec<Message>,
}

impl Ready {
    fn new<T: Storage>(raft: &mut Raft<T>, prev_ss: &SoftState, prev_hs: &HardState) -> Ready {
        let mut rd = Ready {
            entries: raft.raft_log.unstable_entries().unwrap_or(&[]).to_vec(),
            committed_entries: raft.raft_log.next_entries().unwrap_or_else(Vec::new),
            messages: raft.msgs.drain(..).collect(),
            ..Default::default()
        };
        let ss = raft.soft_state();
        if &ss != prev_ss {
            rd.ss = Some(ss);
        }
        let hs = raft.hard_state();
        if &hs != prev_hs {
            rd.hs = Some(hs);
        }
        if raft.raft_log.get_unstable().snapshot.is_some() {
            rd.snapshot = raft.raft_log.get_unstable().snapshot.clone().unwrap();
        }
        rd
    }
}

// RawNode is a thread-unsafe Node.
// The methods of this struct correspond to the methods of Node and are described
// more fully there.
pub struct RawNode<T: Storage> {
    pub raft: Raft<T>,
    prev_ss: SoftState,
    prev_hs: HardState,
}

impl<T: Storage> RawNode<T> {
    // NewRawNode returns a new RawNode given configuration and a list of raft peers.
    pub fn new(config: &Config, store: Arc<T>, peers: &[Peer]) -> Result<RawNode<T>> {
        assert!(config.id != 0, "config.id must not be zero");
        let r = Raft::new(config, store);
        let mut rn = RawNode {
            raft: r,
            prev_hs: Default::default(),
            prev_ss: Default::default(),
        };
        let last_index = rn.raft.get_store().last_index().expect("");
        if last_index == 0 {
            rn.raft.become_follower(1, INVALID_ID);
            let mut ents = Vec::with_capacity(peers.len());
            for (i, peer) in peers.iter().enumerate() {
                let mut cc = ConfChange::new();
                cc.set_change_type(ConfChangeType::AddNode);
                cc.set_node_id(peer.id);
                if peer.context.is_some() {
                    cc.set_context(peer.context.as_ref().unwrap().clone());
                }
                let data = protobuf::Message::write_to_bytes(&cc)
                               .expect("unexpected marshal error");
                let mut e = Entry::new();
                e.set_entry_type(EntryType::EntryConfChange);
                e.set_term(1);
                e.set_index(i as u64 + 1);
                e.set_data(data);
                ents.push(e);
            }
            rn.raft.raft_log.append(&ents);
            rn.raft.raft_log.committed = ents.len() as u64;
            for peer in peers {
                rn.raft.add_node(peer.id);
            }
        }
        rn.prev_ss = rn.raft.soft_state();
        rn.prev_hs = rn.raft.hard_state();
        Ok(rn)
    }

    fn commit_ready(&mut self, rd: Ready) {
        if rd.ss.is_some() {
            self.prev_ss = rd.ss.unwrap();
        }
        if let Some(e) = rd.hs {
            if e != HardState::new() {
                self.prev_hs = e;
            }
        }
        if self.prev_hs.get_commit() != 0 {
            // In most cases, prevHardSt and rd.HardState will be the same
            // because when there are new entries to apply we just sent a
            // HardState with an updated Commit value. However, on initial
            // startup the two are different because we don't send a HardState
            // until something changes, but we do send any un-applied but
            // committed entries (and previously-committed entries may be
            // incorporated into the snapshot, even if rd.CommittedEntries is
            // empty). Therefore we mark all committed entries as applied
            // whether they were included in rd.HardState or not.
            self.raft.raft_log.applied_to(self.prev_hs.get_commit());
        }
        if !rd.entries.is_empty() {
            let e = rd.entries.last().unwrap();
            self.raft.raft_log.stable_to(e.get_index(), e.get_term());
        }
        if rd.snapshot != Snapshot::new() {
            self.raft.raft_log.stable_snap_to(rd.snapshot.get_metadata().get_index());
        }
    }

    // Tick advances the internal logical clock by a single tick.
    pub fn tick(&mut self) {
        self.raft.tick();
    }

    // Campaign causes this RawNode to transition to candidate state.
    pub fn campaign(&mut self) -> Result<()> {
        let mut m = Message::new();
        m.set_msg_type(MessageType::MsgHup);
        self.raft.step(m)
    }

    // Propose proposes data be appended to the raft log.
    pub fn propose(&mut self, data: Vec<u8>) -> Result<()> {
        let mut m = Message::new();
        m.set_msg_type(MessageType::MsgPropose);
        m.set_from(self.raft.id);
        let mut e = Entry::new();
        e.set_data(data);
        m.set_entries(RepeatedField::from_vec(vec![e]));
        self.raft.step(m)
    }

    // ProposeConfChange proposes a config change.
    pub fn propose_conf_change(&mut self, cc: ConfChange) -> Result<()> {
        let data = try!(protobuf::Message::write_to_bytes(&cc));
        let mut m = Message::new();
        m.set_msg_type(MessageType::MsgPropose);
        let mut e = Entry::new();
        e.set_entry_type(EntryType::EntryConfChange);
        e.set_data(data);
        m.set_entries(RepeatedField::from_vec(vec![e]));
        self.raft.step(m)
    }

    pub fn apply_conf_change(&mut self, cc: ConfChange) -> ConfState {
        if cc.get_node_id() == INVALID_ID {
            self.raft.reset_pending_conf();
            let mut cs = ConfState::new();
            cs.set_nodes(self.raft.nodes());
            return cs;
        }
        let nid = cc.get_node_id();
        assert!(cc.has_change_type(), "unexpected conf type");
        match cc.get_change_type() {
            ConfChangeType::AddNode => self.raft.add_node(nid),
            ConfChangeType::RemoveNode => self.raft.remove_node(nid),
            ConfChangeType::UpdateNode => self.raft.reset_pending_conf(),
        }
        let mut cs = ConfState::new();
        cs.set_nodes(self.raft.nodes());
        cs
    }

    // Step advances the state machine using the given message.
    pub fn step(&mut self, m: Message) -> Result<()> {
        // ignore unexpected local messages receiving over network
        if is_local_msg(&m) {
            return Err(Error::StepLocalMsg);
        }
        if self.raft.prs.contains_key(&m.get_from()) || !is_response_msg(&m) {
            return self.raft.step(m);
        }
        Err(Error::StepPeerNotFound)
    }

    // Ready returns the current point-in-time state of this RawNode.
    pub fn ready(&mut self) -> Ready {
        Ready::new(&mut self.raft, &self.prev_ss, &self.prev_hs)
    }

    // HasReady called when RawNode user need to check if any Ready pending.
    // Checking logic in this method should be consistent with Ready.containsUpdates().
    pub fn has_ready(&self) -> bool {
        let raft = &self.raft;
        if raft.soft_state() != self.prev_ss {
            return true;
        }
        let hs = raft.hard_state();
        if hs != HardState::new() && hs != self.prev_hs {
            return true;
        }
        if Some(true) == raft.raft_log.get_unstable().snapshot.as_ref().map(|s| !is_empty_snap(s)) {
            return true;
        }
        if !raft.msgs.is_empty() || raft.raft_log.unstable_entries().is_some() ||
           raft.raft_log.has_next_entries() {
            return true;
        }
        false
    }

    // Advance notifies the RawNode that the application has applied and saved progress in the
    // last Ready results.
    pub fn advance(&mut self, rd: Ready) {
        self.commit_ready(rd);
    }

    // Status returns the current status of the given group.
    pub fn status(&self) -> Status {
        Status::new(&self.raft)
    }

    // ReportUnreachable reports the given node is not reachable for the last send.
    pub fn report_unreachable(&mut self, id: u64) {
        let mut m = Message::new();
        m.set_msg_type(MessageType::MsgUnreachable);
        m.set_from(id);
        // we don't care if it is ok actually
        self.raft.step(m).is_ok();
    }

    // ReportSnapshot reports the status of the sent snapshot.
    pub fn report_snapshot(&mut self, id: u64, status: SnapshotStatus) {
        let rej = status == SnapshotStatus::Failure;
        let mut m = Message::new();
        m.set_msg_type(MessageType::MsgSnapStatus);
        m.set_from(id);
        m.set_reject(rej);
        // we don't care if it is ok actually
        self.raft.step(m).is_ok();
    }
}
<|endoftext|>


<commit_before>// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

mod common;

use common::check_derives;
use gpt_disk_types::{BlockSize, Lba, LbaLe, LbaRangeInclusive};

#[test]
fn test_lba() {
    check_derives::<Lba>();
}

#[test]
fn test_lba_le() {
    check_derives::<LbaLe>();
}

#[test]
fn test_lba_range_inclusive() {
    check_derives::<LbaRangeInclusive>();

    let block_size = BlockSize::new(512).unwrap();

    // Invalid range.
    assert!(LbaRangeInclusive::new(Lba(2), Lba(1)).is_none());

    // Valid range.
    let range = LbaRangeInclusive::new(Lba(1), Lba(2)).unwrap();
    assert_eq!(range.start(), 1);
    assert_eq!(range.end(), 2);
    assert_eq!(
        range.to_byte_range(block_size).unwrap(),
        512..=512 + 512 + (512 - 1)
    );
    assert_eq!(range.to_string(), "1..=2");

    // Test conversion from byte range.

    // Valid.
    assert_eq!(
        range,
        LbaRangeInclusive::from_byte_range(
            512..=512 + 512 + (512 - 1),
            block_size
        )
        .unwrap()
    );
    // Start is not on a block boundary.
    assert!(LbaRangeInclusive::from_byte_range(
        514..=512 + 512 + (512 - 1),
        block_size
    )
    .is_none());
    // End is not on a block boundary.
    assert!(LbaRangeInclusive::from_byte_range(
        512..=512 + 512 + (512 - 2),
        block_size
    )
    .is_none());
}

#[test]
fn test_block_size() {
    check_derives::<BlockSize>();

    assert_eq!(BlockSize::new(512).unwrap().to_u64(), 512);
    assert!(BlockSize::new(0).is_none());
    assert!(BlockSize::new(511).is_none());

    assert_eq!(BlockSize::from_usize(512).unwrap().to_u64(), 512);
    assert!(BlockSize::from_usize(0).is_none());

    assert_eq!(BlockSize::default().to_u64(), 512);
}
<commit_msg>Improve block.rs test coverage<commit_after>// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

mod common;

use common::check_derives;
use gpt_disk_types::{BlockSize, Lba, LbaLe, LbaRangeInclusive, U64Le};

#[test]
fn test_lba() {
    check_derives::<Lba>();
}

#[test]
fn test_lba_le() {
    check_derives::<LbaLe>();

    assert_eq!(LbaLe::from(Lba(123)), LbaLe(U64Le::from_u64(123)));
}

#[test]
fn test_lba_range_inclusive() {
    check_derives::<LbaRangeInclusive>();

    let block_size = BlockSize::new(512).unwrap();

    // Invalid range.
    assert!(LbaRangeInclusive::new(Lba(2), Lba(1)).is_none());

    // Valid range.
    let range = LbaRangeInclusive::new(Lba(1), Lba(2)).unwrap();
    assert_eq!(range.start(), 1);
    assert_eq!(range.end(), 2);
    assert_eq!(
        range.to_byte_range(block_size).unwrap(),
        512..=512 + 512 + (512 - 1)
    );
    assert_eq!(range.to_string(), "1..=2");

    // Test conversion from byte range.

    // Valid.
    assert_eq!(
        range,
        LbaRangeInclusive::from_byte_range(
            512..=512 + 512 + (512 - 1),
            block_size
        )
        .unwrap()
    );
    // Start is not on a block boundary.
    assert!(LbaRangeInclusive::from_byte_range(
        514..=512 + 512 + (512 - 1),
        block_size
    )
    .is_none());
    // End is not on a block boundary.
    assert!(LbaRangeInclusive::from_byte_range(
        512..=512 + 512 + (512 - 2),
        block_size
    )
    .is_none());
}

#[test]
fn test_block_size() {
    check_derives::<BlockSize>();

    assert_eq!(BlockSize::new(512).unwrap().to_u64(), 512);
    assert!(BlockSize::new(0).is_none());
    assert!(BlockSize::new(511).is_none());

    assert_eq!(BlockSize::from_usize(512).unwrap().to_u64(), 512);
    assert!(BlockSize::from_usize(0).is_none());

    assert_eq!(BlockSize::default().to_u32(), 512);
    assert_eq!(BlockSize::default().to_u64(), 512);
}
<|endoftext|>


<commit_before>use config::{Config, Environment, File};
use std::path::MAIN_SEPARATOR;

#[cfg(target_os = "windows")]
const CFG_FILE: &str = "ctehxk2";
#[cfg(not(target_os = "windows"))]
const CFG_FILE: &str = "libctehxk2";

#[derive(Debug, Deserialize)]
pub struct Settings {
    timeout: Option<u64>,
    base_url: String,
    log_level: String,
    log_path: Option<String>,
    ctn: Option<u16>,
    pn: Option<u16>,
}

impl Settings {
    fn new() -> Self {
        let mut settings = Config::new();

        // set defaults
        settings
            .set_default("base_url", "http://localhost:8088/k2/ctapi/")
            .expect("Failed to set default for base_url!")
            .set_default("log_level", "Error")
            .expect("Failed to set default for log_level!");

        // merge with optional config file and env variables
        settings
            .merge(File::with_name(CFG_FILE).required(false))
            .expect("Failed to merge config file!")
            .merge(Environment::with_prefix("k2"))
            .expect("Failed to merge env variables!");

        settings.try_into().expect("Failed to create configuration")
    }

    pub fn base_url() -> String {
        let s = Settings::new();
        let mut url = s.base_url.clone();
        if !url.trim().ends_with("/") {
            url.push_str("/");
        }
        url
    }

    pub fn ctn_or(fallback: u16) -> u16 {
        let s = Settings::new();
        match s.pn {
            Some(_pn) => match s.ctn {
                Some(ctn) => ctn,
                None => fallback,
            },
            None => fallback,
        }
    }

    pub fn pn_or(fallback: u16) -> u16 {
        let s = Settings::new();
        match s.ctn {
            Some(_ctn) => match s.pn {
                Some(pn) => pn,
                None => fallback,
            },
            None => fallback,
        }
    }

    pub fn log_level() -> String {
        let s = Settings::new();
        s.log_level.clone()
    }

    pub fn log_path() -> Option<String> {
        let s = Settings::new();
        match s.log_path {
            Some(mut path) => {
                if !path.trim().ends_with(MAIN_SEPARATOR) {
                    path.push(MAIN_SEPARATOR);
                }

                Some(path)
            }
            None => None,
        }
    }

    pub fn timeout() -> Option<u64> {
        let s = Settings::new();
        match s.timeout {
            Some(timeout) => match timeout {
                0 => None,
                _ => Some(timeout),
            },
            None => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::Settings;
    use std::env;
    use std::path::MAIN_SEPARATOR;

    #[test]
    fn ctn_or_return_env_value_if_pn_is_set_too() {
        env::set_var("K2_CTN", "2");
        env::set_var("K2_PN", "4");

        let ctn = Settings::ctn_or(1);

        assert_eq!(ctn, 2);
    }

    #[test]
    fn ctn_or_return_given_value_if_pn_not_set() {
        env::remove_var("K2_CTN");
        env::remove_var("K2_PN");

        let ctn = Settings::ctn_or(1);

        assert_eq!(ctn, 1);

        env::set_var("K2_CTN", "2");

        let ctn = Settings::ctn_or(1);

        assert_eq!(ctn, 1);
    }

    #[test]
    fn pn_or_return_env_value_if_ctn_is_set_too() {
        env::set_var("K2_CTN", "2");
        env::set_var("K2_PN", "4");

        let pn = Settings::pn_or(1);

        assert_eq!(pn, 4);
    }

    #[test]
    fn pn_or_return_given_value_if_ctn_not_set() {
        env::remove_var("K2_CTN");
        env::remove_var("K2_PN");

        let pn = Settings::pn_or(1);

        assert_eq!(pn, 1);

        env::set_var("K2_PN", "2");

        let pn = Settings::pn_or(1);

        assert_eq!(pn, 1);
    }

    #[test]
    fn base_url_return_env_value() {
        env::set_var("K2_BASE_URL", "a/");

        let url = Settings::base_url();

        assert_eq!(url, "a/");

        env::set_var("K2_BASE_URL", "1");

        let url = Settings::base_url();

        assert_eq!(url, "1/");
    }

    #[test]
    fn base_url_return_default_value_if_no_env() {
        env::remove_var("K2_BASE_URL");

        let url = Settings::base_url();

        assert_eq!(url, "http://localhost:8088/k2/ctapi/");
    }

    #[test]
    fn log_path_returns_env_value() {
        env::set_var("K2_LOG_PATH", "a");

        let path = Settings::log_path();

        assert_eq!(path, Some(format!("a{}", MAIN_SEPARATOR)));
    }

    #[test]
    fn log_path_return_none_if_no_env() {
        env::remove_var("K2_LOG_PATH");

        let path = Settings::log_path();

        assert!(path.is_none());
    }

    #[test]
    fn timeout_returns_env_value() {
        env::set_var("K2_TIMEOUT", "42");

        let timeout = Settings::timeout();

        assert_eq!(timeout, Some(42));
    }

    #[test]
    fn timeout_return_none_if_no_env() {
        env::remove_var("K2_TIMEOUT");

        let timeout = Settings::timeout();

        assert!(timeout.is_none());
    }
}
<commit_msg>ignore env variables if empty<commit_after>use config::{Config, Environment, File};
use std::path::MAIN_SEPARATOR;

#[cfg(target_os = "windows")]
const CFG_FILE: &str = "ctehxk2";
#[cfg(not(target_os = "windows"))]
const CFG_FILE: &str = "libctehxk2";

#[derive(Debug, Deserialize)]
pub struct Settings {
    timeout: Option<u64>,
    base_url: String,
    log_level: String,
    log_path: Option<String>,
    ctn: Option<u16>,
    pn: Option<u16>,
}

impl Settings {
    fn new() -> Self {
        let mut settings = Config::new();

        // set defaults
        settings
            .set_default("base_url", "http://localhost:8088/k2/ctapi/")
            .expect("Failed to set default for base_url!")
            .set_default("log_level", "Error")
            .expect("Failed to set default for log_level!");

        // merge with optional config file and env variables
        settings
            .merge(File::with_name(CFG_FILE).required(false))
            .expect("Failed to merge config file!")
            .merge(Environment::with_prefix("k2").ignore_empty(true))
            .expect("Failed to merge env variables!");

        settings.try_into().expect("Failed to create configuration")
    }

    pub fn base_url() -> String {
        let s = Settings::new();
        let mut url = s.base_url.clone();
        if !url.trim().ends_with("/") {
            url.push_str("/");
        }
        url
    }

    pub fn ctn_or(fallback: u16) -> u16 {
        let s = Settings::new();
        match s.pn {
            Some(_pn) => match s.ctn {
                Some(ctn) => ctn,
                None => fallback,
            },
            None => fallback,
        }
    }

    pub fn pn_or(fallback: u16) -> u16 {
        let s = Settings::new();
        match s.ctn {
            Some(_ctn) => match s.pn {
                Some(pn) => pn,
                None => fallback,
            },
            None => fallback,
        }
    }

    pub fn log_level() -> String {
        let s = Settings::new();
        s.log_level.clone()
    }

    pub fn log_path() -> Option<String> {
        let s = Settings::new();
        match s.log_path {
            Some(mut path) => {
                if !path.trim().ends_with(MAIN_SEPARATOR) {
                    path.push(MAIN_SEPARATOR);
                }

                Some(path)
            }
            None => None,
        }
    }

    pub fn timeout() -> Option<u64> {
        let s = Settings::new();
        match s.timeout {
            Some(timeout) => match timeout {
                0 => None,
                _ => Some(timeout),
            },
            None => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::Settings;
    use std::env;
    use std::path::MAIN_SEPARATOR;

    #[test]
    fn ctn_or_return_env_value_if_pn_is_set_too() {
        env::set_var("K2_CTN", "2");
        env::set_var("K2_PN", "4");

        let ctn = Settings::ctn_or(1);

        assert_eq!(ctn, 2);
    }

    #[test]
    fn ctn_or_return_given_value_if_pn_not_set() {
        env::remove_var("K2_CTN");
        env::remove_var("K2_PN");

        let ctn = Settings::ctn_or(1);

        assert_eq!(ctn, 1);

        env::set_var("K2_CTN", "2");

        let ctn = Settings::ctn_or(1);

        assert_eq!(ctn, 1);
    }

    #[test]
    fn pn_or_return_env_value_if_ctn_is_set_too() {
        env::set_var("K2_CTN", "2");
        env::set_var("K2_PN", "4");

        let pn = Settings::pn_or(1);

        assert_eq!(pn, 4);
    }

    #[test]
    fn pn_or_return_given_value_if_ctn_not_set() {
        env::remove_var("K2_CTN");
        env::remove_var("K2_PN");

        let pn = Settings::pn_or(1);

        assert_eq!(pn, 1);

        env::set_var("K2_PN", "2");

        let pn = Settings::pn_or(1);

        assert_eq!(pn, 1);
    }

    #[test]
    fn base_url_return_env_value() {
        env::set_var("K2_BASE_URL", "a/");

        let url = Settings::base_url();

        assert_eq!(url, "a/");

        env::set_var("K2_BASE_URL", "1");

        let url = Settings::base_url();

        assert_eq!(url, "1/");
    }

    #[test]
    fn base_url_return_default_value_if_no_env() {
        env::remove_var("K2_BASE_URL");

        let url = Settings::base_url();

        assert_eq!(url, "http://localhost:8088/k2/ctapi/");
    }

    #[test]
    fn log_path_returns_env_value() {
        env::set_var("K2_LOG_PATH", "a");

        let path = Settings::log_path();

        assert_eq!(path, Some(format!("a{}", MAIN_SEPARATOR)));
    }

    #[test]
    fn log_path_return_none_if_no_env() {
        env::remove_var("K2_LOG_PATH");

        let path = Settings::log_path();

        assert!(path.is_none());
    }

    #[test]
    fn timeout_returns_env_value() {
        env::set_var("K2_TIMEOUT", "42");

        let timeout = Settings::timeout();

        assert_eq!(timeout, Some(42));
    }

    #[test]
    fn timeout_return_none_if_no_env() {
        env::remove_var("K2_TIMEOUT");

        let timeout = Settings::timeout();

        assert!(timeout.is_none());
    }
}
<|endoftext|>


<commit_before>use nanovg::{Alignment, Color, TextOptions};
use proto;
use sha3::{Digest, Sha3_256};

use input::{InputString, KeyAction, KeyCode, KeyMod};
use render::{Fonts, RenderContext};

use std::cell::RefCell;
use std::time::{Duration, Instant};

pub struct MainLoadingView<'a> {
    pub cur_load_task: &'a RefCell<String>,
}

impl<'a> super::View for MainLoadingView<'a> {
    fn present(&mut self, ctx: &RenderContext) {
        let (w, h) = ctx.size();
        ctx.frame(|f| {
            f.path(
                |p| {
                    let radius = 30.0;
                    p.circle((w / 2.0, h / 2.0), radius);
                    p.fill(Color::from_rgb(200, 100, 0), Default::default());
                },
                Default::default(),
            );
            // Chorus Studio
            {
                let size = 60.0;
                let text = "Chorus Studio";
                let font = ctx.font(Fonts::Moderno);
                let (_, bounds) = f.text_bounds(
                    font,
                    (0.0, 0.0),
                    text,
                    TextOptions {
                        size: size,
                        ..Default::default()
                    },
                );
                let (width, height) = (bounds.max_x, bounds.max_y);
                f.text(
                    font,
                    ((w - width) / 2.0, (h - height) / 8.0),
                    text,
                    TextOptions {
                        size: size,
                        color: Color::from_rgb(255, 255, 255),
                        ..Default::default()
                    },
                );
            }
            // loading
            {
                let size = 28.0;
                let font = ctx.font(Fonts::Vga8);
                let (_, bounds) = f.text_bounds(
                    font,
                    (0.0, 0.0),
                    &*self.cur_load_task.borrow(),
                    TextOptions {
                        size: size,
                        ..Default::default()
                    },
                );
                let (width, height) = (bounds.max_x, bounds.max_y);
                f.text(
                    font,
                    ((w - width) / 2.0, (h - height) / 4.0),
                    &*self.cur_load_task.borrow(),
                    TextOptions {
                        size: size,
                        color: Color::from_rgb(200, 200, 200),
                        ..Default::default()
                    },
                );
            }
        });
    }
}

pub struct MainView<'a> {
    pub user_list: &'a RefCell<Vec<proto::User>>,
}

impl<'a> super::View for MainView<'a> {
    fn present(&mut self, ctx: &RenderContext) {
        ctx.frame(|f| {
            let mut cur_y = 50.0;
            let size = 24.0;

            f.text(
                ctx.font(Fonts::Moderno),
                (5.0, 5.0),
                "Chorus Studio",
                TextOptions {
                    size: 40.0,
                    color: Color::from_rgb(255, 255, 255),
                    ..Default::default()
                },
            );

            for user in self.user_list.borrow().iter() {
                f.text(
                    ctx.font(Fonts::Vga8),
                    (10.0, cur_y),
                    &user.user_name,
                    TextOptions {
                        size: size,
                        color: Color::from_rgb(255, 255, 255),
                        ..Default::default()
                    },
                );
                f.text(
                    ctx.font(Fonts::Inter),
                    (200.0, cur_y + 7.0),
                    match user.activity {
                        proto::UserActivity::Offline => "offline",
                        proto::UserActivity::Away => "away",
                        proto::UserActivity::Active => "online",
                        proto::UserActivity::InProject(ref prj) => prj,
                    },
                    TextOptions {
                        size: 16.0,
                        color: if let proto::UserActivity::InProject(_) = user.activity {
                            Color::from_rgb(200, 155, 200)
                        } else {
                            Color::from_rgb(155, 155, 155)
                        },
                        ..Default::default()
                    },
                );
                cur_y += size;
            }
        });
    }
}

pub struct LoginView<'a> {
    username_input: InputString,
    password_input: InputString,
    username_cursor: usize,
    password_cursor: usize,
    active_input: LoginViewActiveInput,
    on_submit: Box<FnMut(&str, &[u8]) + 'a>,
    invalid_timer_start: Option<Instant>,
    invalid_attempts: usize,
}

#[derive(PartialEq, Eq, Copy, Clone)]
enum LoginViewActiveInput {
    Username,
    Password,
}

impl<'a> LoginView<'a> {
    pub fn new(on_submit: Box<FnMut(&str, &[u8]) + 'a>) -> Self {
        LoginView {
            username_input: InputString::new(),
            password_input: InputString::new(),
            username_cursor: 0,
            password_cursor: 0,
            active_input: LoginViewActiveInput::Username,
            on_submit,
            invalid_timer_start: None,
            invalid_attempts: 0,
        }
    }

    pub fn invalid_login(&mut self) {
        self.invalid_timer_start = Some(Instant::now());
        self.invalid_attempts += 1;
    }

    fn active_input_data(&mut self) -> (&mut InputString, &mut usize) {
        match self.active_input {
            LoginViewActiveInput::Username => (&mut self.username_input, &mut self.username_cursor),
            LoginViewActiveInput::Password => (&mut self.password_input, &mut self.password_cursor),
        }
    }

    fn active_input_str(&self) -> (&str, usize) {
        match self.active_input {
            LoginViewActiveInput::Username => (&self.username_input.as_str(), self.username_cursor),
            LoginViewActiveInput::Password => (self.password_stars(), self.password_cursor),
        }
    }

    fn password_stars(&self) -> &str {
        const STARS: &str =
            "*************************************************************************************";
        &STARS[0..self.password_input.len().min(STARS.len())]
    }
}

impl<'a> super::View for LoginView<'a> {
    fn present(&mut self, ctx: &RenderContext) {
        if let Some(inst) = self.invalid_timer_start.clone() {
            if inst.elapsed() > Duration::from_secs(3) {
                self.invalid_timer_start = None;
            }
        }

        let (w, h) = ctx.size();
        ctx.frame(|f| {
            // "Login"
            f.text(
                ctx.font(Fonts::Moderno),
                (w / 2.0, h / 4.0),
                "Login",
                TextOptions {
                    align: Alignment::new().center().middle(),
                    size: 60.0,
                    color: Color::from_rgb(255, 255, 255),
                    ..Default::default()
                },
            );

            // Button shapes
            let input_width = w / 3.0;
            let input_height = 40f32;
            let input_vert_dist = 20f32;
            f.path(
                |p| {
                    p.rounded_rect(
                        (w / 3.0, h / 2.0 - input_height - input_vert_dist / 2.0),
                        (input_width, input_height),
                        5.0,
                    );
                    p.rounded_rect(
                        (w / 3.0, h / 2.0 + input_vert_dist / 2.0),
                        (input_width, input_height),
                        5.0,
                    );
                    p.fill(Color::from_rgb(128, 30, 80), Default::default());
                },
                Default::default(),
            );

            // Invalid credentials text

            if self.invalid_attempts > 0 {
                f.text(
                    ctx.font(Fonts::Inter),
                    (w / 2.0, h / 2.0 + 100.0),
                    "Your username and / or password is wrong. Please double check.",
                    TextOptions {
                        align: Alignment::new().center().middle(),
                        size: 14.0,
                        color: Color::from_rgb(255, 150, 150),
                        ..Default::default()
                    },
                )
            }

            // Button contents

            let (placeholder_color, content_color) = {
                if self.invalid_timer_start.is_some() {
                    (
                        Color::from_rgb(255, 150, 150),
                        Color::from_rgb(255, 150, 150),
                    )
                } else {
                    (
                        Color::from_rgb(128, 128, 128),
                        Color::from_rgb(255, 255, 255),
                    )
                }
            };

            // Username content
            f.text(
                ctx.font(Fonts::Inter),
                (
                    w / 3.0 + 4.0,
                    h / 2.0 - input_height / 2.0 - input_vert_dist / 2.0,
                ),
                if self.username_input.is_empty() {
                    "Username"
                } else {
                    &self.username_input
                },
                TextOptions {
                    align: Alignment::new().left().middle(),
                    size: input_height - 4.0,
                    color: if self.username_input.is_empty() {
                        placeholder_color
                    } else {
                        content_color
                    },
                    ..Default::default()
                },
            );

            // Password content
            f.text(
                ctx.font(Fonts::Inter),
                (
                    w / 3.0 + 4.0,
                    h / 2.0 + input_height / 2.0 + input_vert_dist / 2.0,
                ),
                if self.password_input.is_empty() {
                    "Password"
                } else {
                    self.password_stars()
                },
                TextOptions {
                    align: Alignment::new().left().middle(),
                    size: input_height - 4.0,
                    color: if self.password_input.is_empty() {
                        placeholder_color
                    } else {
                        content_color
                    },
                    ..Default::default()
                },
            );

            // Input cursor
            {
                let origin = match self.active_input {
                    LoginViewActiveInput::Username => {
                        (w / 3.0, h / 2.0 - input_height - input_vert_dist / 2.0)
                    }
                    LoginViewActiveInput::Password => (w / 3.0, h / 2.0 + input_vert_dist / 2.0),
                };

                let (string, cursor) = self.active_input_str();
                let (adv, _bounds) = f.text_bounds(
                    ctx.font(Fonts::Inter),
                    origin,
                    &string[0..cursor],
                    TextOptions {
                        align: Alignment::new().left().middle(),
                        size: input_height - 4.0,
                        ..Default::default()
                    },
                );
                f.path(
                    |p| {
                        p.rect((origin.0 + adv, origin.1 + 2.0), (2.0, input_height - 4.0));
                        p.fill(Color::from_rgb(255, 0, 0), Default::default());
                    },
                    Default::default(),
                );
            }
        });
    }

    fn on_char_input(&mut self, c: char) {
        if !c.is_control() {
            let (string, cursor) = self.active_input_data();
            string.insert(*cursor, c);
            *cursor += 1;
        }
    }

    fn on_key_input(&mut self, key: KeyAction) {
        if key.was_pressed(KeyCode::Backspace) {
            let (string, cursor) = self.active_input_data();
            if *cursor > 0 {
                string.remove(*cursor - 1);
                *cursor -= 1;
            }
        } else if key.was_pressed(KeyCode::Delete) {
            let (string, cursor) = self.active_input_data();
            if *cursor < string.len() {
                string.remove(*cursor);
            }
        } else if key.was_pressed_once(KeyCode::Return) {
            if self.active_input == LoginViewActiveInput::Username {
                self.active_input = LoginViewActiveInput::Password;
            } else if self.active_input == LoginViewActiveInput::Password {
                (self.on_submit)(
                    &self.username_input.as_str(),
                    Sha3_256::digest(self.password_input.as_str().as_bytes()).as_slice(),
                );
            }
        } else if key.was_pressed_once(KeyCode::Tab) && key.with_modifier(KeyMod::Shift) {
            if self.active_input == LoginViewActiveInput::Password {
                self.active_input = LoginViewActiveInput::Username;
            }
        } else if key.was_pressed_once(KeyCode::Tab) {
            if self.active_input == LoginViewActiveInput::Username {
                self.active_input = LoginViewActiveInput::Password;
            }
        } else if key.was_pressed(KeyCode::Left) {
            let (_string, cursor) = self.active_input_data();
            if *cursor > 0 {
                *cursor -= 1;
            }
        } else if key.was_pressed(KeyCode::Right) {
            let (string, cursor) = self.active_input_data();
            if *cursor < string.len() {
                *cursor += 1;
            }
        } else if key.was_pressed(KeyCode::Home) {
            let (_, cursor) = self.active_input_data();
            *cursor = 0;
        } else if key.was_pressed(KeyCode::End) {
            let (string, cursor) = self.active_input_data();
            *cursor = string.len();
        }
    }
}
<commit_msg>Placeholder text "Username" => "Email"<commit_after>use nanovg::{Alignment, Color, TextOptions};
use proto;
use sha3::{Digest, Sha3_256};

use input::{InputString, KeyAction, KeyCode, KeyMod};
use render::{Fonts, RenderContext};

use std::cell::RefCell;
use std::time::{Duration, Instant};

pub struct MainLoadingView<'a> {
    pub cur_load_task: &'a RefCell<String>,
}

impl<'a> super::View for MainLoadingView<'a> {
    fn present(&mut self, ctx: &RenderContext) {
        let (w, h) = ctx.size();
        ctx.frame(|f| {
            f.path(
                |p| {
                    let radius = 30.0;
                    p.circle((w / 2.0, h / 2.0), radius);
                    p.fill(Color::from_rgb(200, 100, 0), Default::default());
                },
                Default::default(),
            );
            // Chorus Studio
            {
                let size = 60.0;
                let text = "Chorus Studio";
                let font = ctx.font(Fonts::Moderno);
                let (_, bounds) = f.text_bounds(
                    font,
                    (0.0, 0.0),
                    text,
                    TextOptions {
                        size: size,
                        ..Default::default()
                    },
                );
                let (width, height) = (bounds.max_x, bounds.max_y);
                f.text(
                    font,
                    ((w - width) / 2.0, (h - height) / 8.0),
                    text,
                    TextOptions {
                        size: size,
                        color: Color::from_rgb(255, 255, 255),
                        ..Default::default()
                    },
                );
            }
            // loading
            {
                let size = 28.0;
                let font = ctx.font(Fonts::Vga8);
                let (_, bounds) = f.text_bounds(
                    font,
                    (0.0, 0.0),
                    &*self.cur_load_task.borrow(),
                    TextOptions {
                        size: size,
                        ..Default::default()
                    },
                );
                let (width, height) = (bounds.max_x, bounds.max_y);
                f.text(
                    font,
                    ((w - width) / 2.0, (h - height) / 4.0),
                    &*self.cur_load_task.borrow(),
                    TextOptions {
                        size: size,
                        color: Color::from_rgb(200, 200, 200),
                        ..Default::default()
                    },
                );
            }
        });
    }
}

pub struct MainView<'a> {
    pub user_list: &'a RefCell<Vec<proto::User>>,
}

impl<'a> super::View for MainView<'a> {
    fn present(&mut self, ctx: &RenderContext) {
        ctx.frame(|f| {
            let mut cur_y = 50.0;
            let size = 24.0;

            f.text(
                ctx.font(Fonts::Moderno),
                (5.0, 5.0),
                "Chorus Studio",
                TextOptions {
                    size: 40.0,
                    color: Color::from_rgb(255, 255, 255),
                    ..Default::default()
                },
            );

            for user in self.user_list.borrow().iter() {
                f.text(
                    ctx.font(Fonts::Vga8),
                    (10.0, cur_y),
                    &user.user_name,
                    TextOptions {
                        size: size,
                        color: Color::from_rgb(255, 255, 255),
                        ..Default::default()
                    },
                );
                f.text(
                    ctx.font(Fonts::Inter),
                    (200.0, cur_y + 7.0),
                    match user.activity {
                        proto::UserActivity::Offline => "offline",
                        proto::UserActivity::Away => "away",
                        proto::UserActivity::Active => "online",
                        proto::UserActivity::InProject(ref prj) => prj,
                    },
                    TextOptions {
                        size: 16.0,
                        color: if let proto::UserActivity::InProject(_) = user.activity {
                            Color::from_rgb(200, 155, 200)
                        } else {
                            Color::from_rgb(155, 155, 155)
                        },
                        ..Default::default()
                    },
                );
                cur_y += size;
            }
        });
    }
}

pub struct LoginView<'a> {
    username_input: InputString,
    password_input: InputString,
    username_cursor: usize,
    password_cursor: usize,
    active_input: LoginViewActiveInput,
    on_submit: Box<FnMut(&str, &[u8]) + 'a>,
    invalid_timer_start: Option<Instant>,
    invalid_attempts: usize,
}

#[derive(PartialEq, Eq, Copy, Clone)]
enum LoginViewActiveInput {
    Username,
    Password,
}

impl<'a> LoginView<'a> {
    pub fn new(on_submit: Box<FnMut(&str, &[u8]) + 'a>) -> Self {
        LoginView {
            username_input: InputString::new(),
            password_input: InputString::new(),
            username_cursor: 0,
            password_cursor: 0,
            active_input: LoginViewActiveInput::Username,
            on_submit,
            invalid_timer_start: None,
            invalid_attempts: 0,
        }
    }

    pub fn invalid_login(&mut self) {
        self.invalid_timer_start = Some(Instant::now());
        self.invalid_attempts += 1;
    }

    fn active_input_data(&mut self) -> (&mut InputString, &mut usize) {
        match self.active_input {
            LoginViewActiveInput::Username => (&mut self.username_input, &mut self.username_cursor),
            LoginViewActiveInput::Password => (&mut self.password_input, &mut self.password_cursor),
        }
    }

    fn active_input_str(&self) -> (&str, usize) {
        match self.active_input {
            LoginViewActiveInput::Username => (&self.username_input.as_str(), self.username_cursor),
            LoginViewActiveInput::Password => (self.password_stars(), self.password_cursor),
        }
    }

    fn password_stars(&self) -> &str {
        const STARS: &str =
            "*************************************************************************************";
        &STARS[0..self.password_input.len().min(STARS.len())]
    }
}

impl<'a> super::View for LoginView<'a> {
    fn present(&mut self, ctx: &RenderContext) {
        if let Some(inst) = self.invalid_timer_start.clone() {
            if inst.elapsed() > Duration::from_secs(3) {
                self.invalid_timer_start = None;
            }
        }

        let (w, h) = ctx.size();
        ctx.frame(|f| {
            // "Login"
            f.text(
                ctx.font(Fonts::Moderno),
                (w / 2.0, h / 4.0),
                "Login",
                TextOptions {
                    align: Alignment::new().center().middle(),
                    size: 60.0,
                    color: Color::from_rgb(255, 255, 255),
                    ..Default::default()
                },
            );

            // Button shapes
            let input_width = w / 3.0;
            let input_height = 40f32;
            let input_vert_dist = 20f32;
            f.path(
                |p| {
                    p.rounded_rect(
                        (w / 3.0, h / 2.0 - input_height - input_vert_dist / 2.0),
                        (input_width, input_height),
                        5.0,
                    );
                    p.rounded_rect(
                        (w / 3.0, h / 2.0 + input_vert_dist / 2.0),
                        (input_width, input_height),
                        5.0,
                    );
                    p.fill(Color::from_rgb(128, 30, 80), Default::default());
                },
                Default::default(),
            );

            // Invalid credentials text

            if self.invalid_attempts > 0 {
                f.text(
                    ctx.font(Fonts::Inter),
                    (w / 2.0, h / 2.0 + 100.0),
                    "Your username and / or password is wrong. Please double check.",
                    TextOptions {
                        align: Alignment::new().center().middle(),
                        size: 14.0,
                        color: Color::from_rgb(255, 150, 150),
                        ..Default::default()
                    },
                )
            }

            // Button contents

            let (placeholder_color, content_color) = {
                if self.invalid_timer_start.is_some() {
                    (
                        Color::from_rgb(255, 150, 150),
                        Color::from_rgb(255, 150, 150),
                    )
                } else {
                    (
                        Color::from_rgb(128, 128, 128),
                        Color::from_rgb(255, 255, 255),
                    )
                }
            };

            // Username content
            f.text(
                ctx.font(Fonts::Inter),
                (
                    w / 3.0 + 4.0,
                    h / 2.0 - input_height / 2.0 - input_vert_dist / 2.0,
                ),
                if self.username_input.is_empty() {
                    "Email"
                } else {
                    &self.username_input
                },
                TextOptions {
                    align: Alignment::new().left().middle(),
                    size: input_height - 4.0,
                    color: if self.username_input.is_empty() {
                        placeholder_color
                    } else {
                        content_color
                    },
                    ..Default::default()
                },
            );

            // Password content
            f.text(
                ctx.font(Fonts::Inter),
                (
                    w / 3.0 + 4.0,
                    h / 2.0 + input_height / 2.0 + input_vert_dist / 2.0,
                ),
                if self.password_input.is_empty() {
                    "Password"
                } else {
                    self.password_stars()
                },
                TextOptions {
                    align: Alignment::new().left().middle(),
                    size: input_height - 4.0,
                    color: if self.password_input.is_empty() {
                        placeholder_color
                    } else {
                        content_color
                    },
                    ..Default::default()
                },
            );

            // Input cursor
            {
                let origin = match self.active_input {
                    LoginViewActiveInput::Username => {
                        (w / 3.0, h / 2.0 - input_height - input_vert_dist / 2.0)
                    }
                    LoginViewActiveInput::Password => (w / 3.0, h / 2.0 + input_vert_dist / 2.0),
                };

                let (string, cursor) = self.active_input_str();
                let (adv, _bounds) = f.text_bounds(
                    ctx.font(Fonts::Inter),
                    origin,
                    &string[0..cursor],
                    TextOptions {
                        align: Alignment::new().left().middle(),
                        size: input_height - 4.0,
                        ..Default::default()
                    },
                );
                f.path(
                    |p| {
                        p.rect((origin.0 + adv, origin.1 + 2.0), (2.0, input_height - 4.0));
                        p.fill(Color::from_rgb(255, 0, 0), Default::default());
                    },
                    Default::default(),
                );
            }
        });
    }

    fn on_char_input(&mut self, c: char) {
        if !c.is_control() {
            let (string, cursor) = self.active_input_data();
            string.insert(*cursor, c);
            *cursor += 1;
        }
    }

    fn on_key_input(&mut self, key: KeyAction) {
        if key.was_pressed(KeyCode::Backspace) {
            let (string, cursor) = self.active_input_data();
            if *cursor > 0 {
                string.remove(*cursor - 1);
                *cursor -= 1;
            }
        } else if key.was_pressed(KeyCode::Delete) {
            let (string, cursor) = self.active_input_data();
            if *cursor < string.len() {
                string.remove(*cursor);
            }
        } else if key.was_pressed_once(KeyCode::Return) {
            if self.active_input == LoginViewActiveInput::Username {
                self.active_input = LoginViewActiveInput::Password;
            } else if self.active_input == LoginViewActiveInput::Password {
                (self.on_submit)(
                    &self.username_input.as_str(),
                    Sha3_256::digest(self.password_input.as_str().as_bytes()).as_slice(),
                );
            }
        } else if key.was_pressed_once(KeyCode::Tab) && key.with_modifier(KeyMod::Shift) {
            if self.active_input == LoginViewActiveInput::Password {
                self.active_input = LoginViewActiveInput::Username;
            }
        } else if key.was_pressed_once(KeyCode::Tab) {
            if self.active_input == LoginViewActiveInput::Username {
                self.active_input = LoginViewActiveInput::Password;
            }
        } else if key.was_pressed(KeyCode::Left) {
            let (_string, cursor) = self.active_input_data();
            if *cursor > 0 {
                *cursor -= 1;
            }
        } else if key.was_pressed(KeyCode::Right) {
            let (string, cursor) = self.active_input_data();
            if *cursor < string.len() {
                *cursor += 1;
            }
        } else if key.was_pressed(KeyCode::Home) {
            let (_, cursor) = self.active_input_data();
            *cursor = 0;
        } else if key.was_pressed(KeyCode::End) {
            let (string, cursor) = self.active_input_data();
            *cursor = string.len();
        }
    }
}
<|endoftext|>