<commit_before>// This file is part of Grust, GObject introspection bindings for Rust
//
// Copyright (C) 2013, 2014  Mikhail Zabaluev <mikhail.zabaluev@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

#![crate_name = "grust-Gio-2_0"]

#![crate_type = "lib"]

extern crate grust;
extern crate "grust-GLib-2_0" as glib;
extern crate "grust-GObject-2_0" as gobject;
extern crate libc;

use grust::gtype::GType;
use grust::marker;
use grust::object;
use grust::refcount;
use grust::types;
use grust::utf8;

use cast::AsyncResult as _cast_AsyncResult;
use cast::Cancellable as _cast_Cancellable;

#[repr(C)]
pub struct AsyncResult {
    marker: marker::ObjectMarker
}

#[repr(C)]
pub struct File {
    marker: marker::ObjectMarker
}

#[repr(C)]
pub struct Cancellable {
    parent_instance: gobject::Object,
    _priv: types::gpointer
}

#[repr(C)]
pub struct InputStream {
    parent_instance: gobject::Object,
    _priv: types::gpointer
}

#[repr(C)]
pub struct FileInputStream {
    parent_instance: InputStream,
    _priv: types::gpointer
}

pub mod enums {

    pub mod io_error {

        use grust::error;
        use grust::quark;
        use std::fmt;
        use std::sync::atomic;

        #[deriving(FromPrimitive,PartialEq,Eq)]
        #[repr(C)]
        pub enum IOErrorEnum {
            Failed = 0,
            NotFound = 1,
            Exists = 2,
            // ...
        }

        impl fmt::Show for IOErrorEnum {
            fn fmt(&self, format: &mut fmt::Formatter) -> Result<(), fmt::FormatError> {
                let s: &'static str = match *self {
                    Failed   => "failed",
                    NotFound => "not-found",
                    Exists   => "exists",
                    // ...
                };
                s.fmt(format)
            }
        }

        pub static mut ERROR_QUARK: atomic::AtomicUint = atomic::INIT_ATOMIC_UINT;

        impl error::ErrorDomain for IOErrorEnum {
            fn error_domain(_: Option<IOErrorEnum>) -> quark::Quark {
                unsafe {
                    let mut d = ERROR_QUARK.load(atomic::Relaxed)
                                as quark::Quark;
                    if d == 0 {
                        d = quark::from_static_string("g-io-error-quark\0");
                        ERROR_QUARK.store(d as uint, atomic::Relaxed);
                    }
                    d
                }
            }
        }

        impl ToPrimitive for IOErrorEnum {
            fn to_int(&self) -> Option<int> { Some(*self as int) }
            fn to_i64(&self) -> Option<i64> { Some(*self as i64) }
            fn to_u64(&self) -> Option<u64> { Some(*self as u64) }
        }
    }
}

#[allow(ctypes)]
pub mod raw {
    use grust::types::{gchar,gint,gpointer};
    use grust::gtype::GType;
    use grust::error::raw::GError;
    use gobject;
    use libc;

    pub type GAsyncResult = super::AsyncResult;
    pub type GFile = super::File;
    pub type GCancellable = super::Cancellable;
    pub type GInputStream = super::InputStream;
    pub type GFileInputStream = super::FileInputStream;

    pub type GAsyncReadyCallback = extern "C" fn(source_object: *mut gobject::raw::GObject,
                                                 res: *mut GAsyncResult,
                                                 user_data: gpointer);

    #[link(name="gio-2.0")]
    extern {
        pub fn g_async_result_get_type() -> GType;
        pub fn g_file_get_type() -> GType;
        pub fn g_file_new_for_path(path: *const gchar) -> *mut GFile;
        pub fn g_file_get_path(file: *mut GFile) -> *mut libc::c_char;
        pub fn g_file_read_async(file: *mut GFile,
                                 io_priority: gint,
                                 cancellable: *mut GCancellable,
                                 callback: GAsyncReadyCallback,
                                 user_data: gpointer);
        pub fn g_file_read_finish(file: *mut GFile,
                                  res: *mut GAsyncResult,
                                  error: *mut *mut GError)
                                  -> *mut GFileInputStream;
    }
}

pub mod async {

    use gobject;

    pub type AsyncReadyCallback =
                proc<'a>(&'a mut gobject::Object,
                         &'a mut super::AsyncResult)
                        : Send;

}

mod async_shim {

    use grust::types;
    use super::async;
    use super::raw;
    use gobject;
    use std::mem;

    pub extern "C" fn async_ready_callback(source_object: *mut gobject::raw::GObject,
                                           res: *mut raw::GAsyncResult,
                                           user_data: types::gpointer) {
        unsafe {
            let callback: Box<async::AsyncReadyCallback> =
                    mem::transmute(user_data);

            (*callback)(&mut *source_object, &mut *res);
        }
    }
}

pub mod cast {

    use gobject;

    pub trait AsyncResult {
        fn as_gio_async_result<'a>(&'a self) -> &'a super::AsyncResult;
        fn as_mut_gio_async_result<'a>(&'a mut self) -> &'a mut super::AsyncResult;
    }

    pub trait Cancellable : gobject::cast::Object {
        fn as_gio_cancellable<'a>(&'a self) -> &'a super::Cancellable;
        fn as_mut_gio_cancellable<'a>(&'a mut self) -> &'a mut super::Cancellable;
    }

    pub trait InputStream : gobject::cast::Object {
    }

    pub trait FileInputStream : InputStream {
    }

    pub trait File {
        fn as_gio_file<'a>(&'a self) -> &'a super::File;
        fn as_mut_gio_file<'a>(&'a mut self) -> &'a mut super::File;
    }
}

impl File {

    pub fn new_for_path<T: utf8::WithUTF8>(path: T) -> refcount::Ref<File> {
        let ret = path.with_utf8_c_str(|p| {
            unsafe {
                raw::g_file_new_for_path(p)
            }
        });
        unsafe { refcount::raw::ref_from_ptr(ret) }
    }

    pub fn get_path<'a>(&mut self) -> utf8::UTF8Buf {
        unsafe {
            let ret = raw::g_file_get_path(self);
            utf8::UTF8Buf::wrap(ret)
        }
    }

    pub fn read_async(&mut self,
                      io_priority: types::gint,
                      cancellable: Option<&mut Cancellable>,
                      callback: Box<async::AsyncReadyCallback>) {
        unsafe {
            let raw_cancellable =
                match cancellable {
                    Some(c) => c.as_mut_gio_cancellable() as *mut raw::GCancellable,
                    None    => std::ptr::null_mut::<raw::GCancellable>()
                };
            let raw_callback: types::gpointer = std::mem::transmute(callback);

            raw::g_file_read_async(self,
                                   io_priority as libc::c_int,
                                   raw_cancellable,
                                   async_shim::async_ready_callback,
                                   raw_callback);
        }
    }

    pub fn read_finish(&mut self, res: &mut AsyncResult)
                      -> std::result::Result<refcount::Ref<FileInputStream>,
                                             grust::error::Error> {
        unsafe {
            let mut err: grust::error::Error = grust::error::unset();
            let ret = raw::g_file_read_finish(self,
                                              res.as_mut_gio_async_result(),
                                              err.slot_ptr());
            if err.is_set() {
                std::result::Err(err)
            } else {
                std::result::Ok(refcount::raw::ref_from_ptr(ret))
            }
        }
    }
}

impl object::ObjectType for AsyncResult {
    fn get_type(&self) -> GType {
        unsafe {
            raw::g_async_result_get_type()
        }
    }
}

impl object::ObjectType for File {
    fn get_type(&self) -> GType {
        unsafe {
            raw::g_file_get_type()
        }
    }
}

impl refcount::Refcount for File {
    fn refcount_funcs(&self) -> &'static refcount::RefcountFuncs {
        &object::REFCOUNT_FUNCS
    }
}

impl refcount::Refcount for FileInputStream {
    fn refcount_funcs(&self) -> &'static refcount::RefcountFuncs {
        &object::REFCOUNT_FUNCS
    }
}

impl cast::AsyncResult for AsyncResult {
    fn as_gio_async_result<'a>(&'a self) -> &'a AsyncResult { self }
    fn as_mut_gio_async_result<'a>(&'a mut self) -> &'a mut AsyncResult { self }
}

impl gobject::cast::Object for Cancellable {
    fn as_gobject_object<'a>(&'a self) -> &'a gobject::Object {
        &self.parent_instance
    }
    fn as_mut_gobject_object<'a>(&'a mut self) -> &'a mut gobject::Object {
        &mut self.parent_instance
    }
}

impl cast::Cancellable for Cancellable {
    fn as_gio_cancellable<'a>(&'a self) -> &'a Cancellable { self }
    fn as_mut_gio_cancellable<'a>(&'a mut self) -> &'a mut Cancellable { self }
}

impl cast::File for File {
    fn as_gio_file<'a>(&'a self) -> &'a File { self }
    fn as_mut_gio_file<'a>(&'a mut self) -> &'a mut File { self }
}

impl gobject::cast::Object for InputStream {
    fn as_gobject_object<'a>(&'a self) -> &'a gobject::Object {
        &self.parent_instance
    }
    fn as_mut_gobject_object<'a>(&'a mut self) -> &'a mut gobject::Object {
        &mut self.parent_instance
    }
}

impl gobject::cast::Object for FileInputStream {
    fn as_gobject_object<'a>(&'a self) -> &'a gobject::Object {
        self.parent_instance.as_gobject_object()
    }
    fn as_mut_gobject_object<'a>(&'a mut self) -> &'a mut gobject::Object {
        self.parent_instance.as_mut_gobject_object()
    }
}
<commit_msg>Make the error quark private<commit_after>// This file is part of Grust, GObject introspection bindings for Rust
//
// Copyright (C) 2013, 2014  Mikhail Zabaluev <mikhail.zabaluev@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

#![crate_name = "grust-Gio-2_0"]

#![crate_type = "lib"]

extern crate grust;
extern crate "grust-GLib-2_0" as glib;
extern crate "grust-GObject-2_0" as gobject;
extern crate libc;

use grust::gtype::GType;
use grust::marker;
use grust::object;
use grust::refcount;
use grust::types;
use grust::utf8;

use cast::AsyncResult as _cast_AsyncResult;
use cast::Cancellable as _cast_Cancellable;

#[repr(C)]
pub struct AsyncResult {
    marker: marker::ObjectMarker
}

#[repr(C)]
pub struct File {
    marker: marker::ObjectMarker
}

#[repr(C)]
pub struct Cancellable {
    parent_instance: gobject::Object,
    _priv: types::gpointer
}

#[repr(C)]
pub struct InputStream {
    parent_instance: gobject::Object,
    _priv: types::gpointer
}

#[repr(C)]
pub struct FileInputStream {
    parent_instance: InputStream,
    _priv: types::gpointer
}

pub mod enums {

    pub mod io_error {

        use grust::error;
        use grust::quark;
        use std::fmt;
        use std::sync::atomic;

        #[deriving(FromPrimitive,PartialEq,Eq)]
        #[repr(C)]
        pub enum IOErrorEnum {
            Failed = 0,
            NotFound = 1,
            Exists = 2,
            // ...
        }

        impl fmt::Show for IOErrorEnum {
            fn fmt(&self, format: &mut fmt::Formatter) -> Result<(), fmt::FormatError> {
                let s: &'static str = match *self {
                    Failed   => "failed",
                    NotFound => "not-found",
                    Exists   => "exists",
                    // ...
                };
                s.fmt(format)
            }
        }

        static mut ERROR_QUARK: atomic::AtomicUint = atomic::INIT_ATOMIC_UINT;

        impl error::ErrorDomain for IOErrorEnum {
            fn error_domain(_: Option<IOErrorEnum>) -> quark::Quark {
                unsafe {
                    let mut d = ERROR_QUARK.load(atomic::Relaxed)
                                as quark::Quark;
                    if d == 0 {
                        d = quark::from_static_string("g-io-error-quark\0");
                        ERROR_QUARK.store(d as uint, atomic::Relaxed);
                    }
                    d
                }
            }
        }

        impl ToPrimitive for IOErrorEnum {
            fn to_int(&self) -> Option<int> { Some(*self as int) }
            fn to_i64(&self) -> Option<i64> { Some(*self as i64) }
            fn to_u64(&self) -> Option<u64> { Some(*self as u64) }
        }
    }
}

#[allow(ctypes)]
pub mod raw {
    use grust::types::{gchar,gint,gpointer};
    use grust::gtype::GType;
    use grust::error::raw::GError;
    use gobject;
    use libc;

    pub type GAsyncResult = super::AsyncResult;
    pub type GFile = super::File;
    pub type GCancellable = super::Cancellable;
    pub type GInputStream = super::InputStream;
    pub type GFileInputStream = super::FileInputStream;

    pub type GAsyncReadyCallback = extern "C" fn(source_object: *mut gobject::raw::GObject,
                                                 res: *mut GAsyncResult,
                                                 user_data: gpointer);

    #[link(name="gio-2.0")]
    extern {
        pub fn g_async_result_get_type() -> GType;
        pub fn g_file_get_type() -> GType;
        pub fn g_file_new_for_path(path: *const gchar) -> *mut GFile;
        pub fn g_file_get_path(file: *mut GFile) -> *mut libc::c_char;
        pub fn g_file_read_async(file: *mut GFile,
                                 io_priority: gint,
                                 cancellable: *mut GCancellable,
                                 callback: GAsyncReadyCallback,
                                 user_data: gpointer);
        pub fn g_file_read_finish(file: *mut GFile,
                                  res: *mut GAsyncResult,
                                  error: *mut *mut GError)
                                  -> *mut GFileInputStream;
    }
}

pub mod async {

    use gobject;

    pub type AsyncReadyCallback =
                proc<'a>(&'a mut gobject::Object,
                         &'a mut super::AsyncResult)
                        : Send;

}

mod async_shim {

    use grust::types;
    use super::async;
    use super::raw;
    use gobject;
    use std::mem;

    pub extern "C" fn async_ready_callback(source_object: *mut gobject::raw::GObject,
                                           res: *mut raw::GAsyncResult,
                                           user_data: types::gpointer) {
        unsafe {
            let callback: Box<async::AsyncReadyCallback> =
                    mem::transmute(user_data);

            (*callback)(&mut *source_object, &mut *res);
        }
    }
}

pub mod cast {

    use gobject;

    pub trait AsyncResult {
        fn as_gio_async_result<'a>(&'a self) -> &'a super::AsyncResult;
        fn as_mut_gio_async_result<'a>(&'a mut self) -> &'a mut super::AsyncResult;
    }

    pub trait Cancellable : gobject::cast::Object {
        fn as_gio_cancellable<'a>(&'a self) -> &'a super::Cancellable;
        fn as_mut_gio_cancellable<'a>(&'a mut self) -> &'a mut super::Cancellable;
    }

    pub trait InputStream : gobject::cast::Object {
    }

    pub trait FileInputStream : InputStream {
    }

    pub trait File {
        fn as_gio_file<'a>(&'a self) -> &'a super::File;
        fn as_mut_gio_file<'a>(&'a mut self) -> &'a mut super::File;
    }
}

impl File {

    pub fn new_for_path<T: utf8::WithUTF8>(path: T) -> refcount::Ref<File> {
        let ret = path.with_utf8_c_str(|p| {
            unsafe {
                raw::g_file_new_for_path(p)
            }
        });
        unsafe { refcount::raw::ref_from_ptr(ret) }
    }

    pub fn get_path<'a>(&mut self) -> utf8::UTF8Buf {
        unsafe {
            let ret = raw::g_file_get_path(self);
            utf8::UTF8Buf::wrap(ret)
        }
    }

    pub fn read_async(&mut self,
                      io_priority: types::gint,
                      cancellable: Option<&mut Cancellable>,
                      callback: Box<async::AsyncReadyCallback>) {
        unsafe {
            let raw_cancellable =
                match cancellable {
                    Some(c) => c.as_mut_gio_cancellable() as *mut raw::GCancellable,
                    None    => std::ptr::null_mut::<raw::GCancellable>()
                };
            let raw_callback: types::gpointer = std::mem::transmute(callback);

            raw::g_file_read_async(self,
                                   io_priority as libc::c_int,
                                   raw_cancellable,
                                   async_shim::async_ready_callback,
                                   raw_callback);
        }
    }

    pub fn read_finish(&mut self, res: &mut AsyncResult)
                      -> std::result::Result<refcount::Ref<FileInputStream>,
                                             grust::error::Error> {
        unsafe {
            let mut err: grust::error::Error = grust::error::unset();
            let ret = raw::g_file_read_finish(self,
                                              res.as_mut_gio_async_result(),
                                              err.slot_ptr());
            if err.is_set() {
                std::result::Err(err)
            } else {
                std::result::Ok(refcount::raw::ref_from_ptr(ret))
            }
        }
    }
}

impl object::ObjectType for AsyncResult {
    fn get_type(&self) -> GType {
        unsafe {
            raw::g_async_result_get_type()
        }
    }
}

impl object::ObjectType for File {
    fn get_type(&self) -> GType {
        unsafe {
            raw::g_file_get_type()
        }
    }
}

impl refcount::Refcount for File {
    fn refcount_funcs(&self) -> &'static refcount::RefcountFuncs {
        &object::REFCOUNT_FUNCS
    }
}

impl refcount::Refcount for FileInputStream {
    fn refcount_funcs(&self) -> &'static refcount::RefcountFuncs {
        &object::REFCOUNT_FUNCS
    }
}

impl cast::AsyncResult for AsyncResult {
    fn as_gio_async_result<'a>(&'a self) -> &'a AsyncResult { self }
    fn as_mut_gio_async_result<'a>(&'a mut self) -> &'a mut AsyncResult { self }
}

impl gobject::cast::Object for Cancellable {
    fn as_gobject_object<'a>(&'a self) -> &'a gobject::Object {
        &self.parent_instance
    }
    fn as_mut_gobject_object<'a>(&'a mut self) -> &'a mut gobject::Object {
        &mut self.parent_instance
    }
}

impl cast::Cancellable for Cancellable {
    fn as_gio_cancellable<'a>(&'a self) -> &'a Cancellable { self }
    fn as_mut_gio_cancellable<'a>(&'a mut self) -> &'a mut Cancellable { self }
}

impl cast::File for File {
    fn as_gio_file<'a>(&'a self) -> &'a File { self }
    fn as_mut_gio_file<'a>(&'a mut self) -> &'a mut File { self }
}

impl gobject::cast::Object for InputStream {
    fn as_gobject_object<'a>(&'a self) -> &'a gobject::Object {
        &self.parent_instance
    }
    fn as_mut_gobject_object<'a>(&'a mut self) -> &'a mut gobject::Object {
        &mut self.parent_instance
    }
}

impl gobject::cast::Object for FileInputStream {
    fn as_gobject_object<'a>(&'a self) -> &'a gobject::Object {
        self.parent_instance.as_gobject_object()
    }
    fn as_mut_gobject_object<'a>(&'a mut self) -> &'a mut gobject::Object {
        self.parent_instance.as_mut_gobject_object()
    }
}
<|endoftext|>


<commit_before>extern crate array_ext;
extern crate custom_gl as gl;
extern crate custom_egl as egl;

mod types;
#[macro_use]
mod typeinfo;
mod shader;
mod eglw;

use std::mem;
use std::ffi::CStr;
use std::ptr;
use std::cell::Cell;
use gl::types::*;
use typeinfo::TypeInfo;
use array_ext::Array;
use shader::{Shader, Program};

pub use egl::NativeDisplayType;
pub use egl::NativeWindowType;
pub use eglw::Surface;

#[derive(Debug, Clone, Copy)]
#[repr(C)]
struct Vertex
{
    pos: [i16; 2],
    col: [f32; 4],
    texc: [f32; 2],
}

impl_typeinfo!(Vertex, pos, col, texc);

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
enum PrimType
{
    Points = gl::POINTS,
    Lines = gl::LINES,
    LineStrip = gl::LINE_STRIP,
    Triangles = gl::TRIANGLES,
}

pub struct DrawEngine
{
    egl_disp: eglw::Display,
    prog: Program,
    vbo_vert: GLuint,
    vbo_idx: GLuint,
    default_tex: GLuint,
    cur_ty: Cell<PrimType>,
    vert_off: Cell<usize>,
    idx_off: Cell<usize>,
    vert_len: Cell<usize>,
    idx_len: Cell<usize>,
    cur_tex: Cell<GLuint>,
    pub max_verts: usize,
    pub max_idxs: usize,
}

impl DrawEngine
{
    pub fn new(xdisp: NativeDisplayType) -> Result<Self, &'static str>
    {
        let egl_disp = try!(eglw::Display::new(xdisp));

        unsafe
        {
            let vendor = CStr::from_ptr(gl::GetString(gl::VENDOR) as *const _);
            let renderer = CStr::from_ptr(gl::GetString(gl::RENDERER) as *const _);
            let version = CStr::from_ptr(gl::GetString(gl::VERSION) as *const _);
            let exts = CStr::from_ptr(gl::GetString(gl::EXTENSIONS) as *const _);
            println!("GL vendor: {:?}\nGL renderer: {:?}\nGL version: {:?}\nGL extensions: {:?}",
                vendor, renderer, version, exts);
        }

        unsafe
        {
            let prog = Program::new(&[
                Shader::new(gl::VERTEX_SHADER, &[include_str!("test.vert.glsl")]).unwrap_or_else(|e| panic!("vert: {}", e)),
                Shader::new(gl::FRAGMENT_SHADER, &[include_str!("test.frag.glsl")]).unwrap_or_else(|e| panic!("frag: {}", e)),
            ]).unwrap_or_else(|e| panic!("link: {}", e));
            prog.set_active();

            // vertex buffer
            let mut vbo_vert = 0;
            gl::GenBuffers(1, &mut vbo_vert);
            gl::BindBuffer(gl::ARRAY_BUFFER, vbo_vert);
            let size = mem::size_of::<Vertex>();
            Vertex::visit_fields(|name, offset, count, ty| {
                let num = prog.get_attrib(name).unwrap();
                gl::VertexAttribPointer(num, count as GLint, ty as GLenum, gl::FALSE, size as GLsizei, offset as *const _);
                gl::EnableVertexAttribArray(num);
            });

            // index buffer
            let mut vbo_idx = 0;
            gl::GenBuffers(1, &mut vbo_idx);
            gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, vbo_idx);

            // 1x1 white texture
            let mut tex = 0;
            gl::GenTextures(1, &mut tex);
            gl::BindTexture(gl::TEXTURE_2D, tex);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as GLint);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint);
            gl::TexImage2D(gl::TEXTURE_2D, 0, gl::LUMINANCE as GLint, 1, 1, 0, gl::LUMINANCE, gl::UNSIGNED_BYTE, [255u8].as_ptr() as *const _);

            let eng = DrawEngine{
                egl_disp: egl_disp,
                prog: prog,
                vbo_vert: vbo_vert,
                vbo_idx: vbo_idx,
                default_tex: tex,
                cur_ty: Cell::new(PrimType::Triangles),
                vert_off: Cell::new(0),
                idx_off: Cell::new(0),
                vert_len: Cell::new(0),
                idx_len: Cell::new(0),
                cur_tex: Cell::new(0),
                // allocate 1mb
                max_verts: 32768,
                max_idxs: 65536,
            };

            eng.alloc_vert();
            eng.alloc_idx();

            Ok(eng)
        }
    }

    pub fn create_window_surface(&self, win: NativeWindowType) -> Result<Surface, &'static str>
    {
        self.egl_disp.create_window_surface(win)
    }

    pub fn begin_draw<'a>(&'a self, surface: &'a Surface, (width, height): (u32, u32)) -> DrawContext
    {
        if !surface.is_current()
        {
            let (w, h) = (width as f32, height as f32);
            let tf = [
                2.0/w,    0.0, 0.0, 0.0,
                  0.0, -2.0/h, 0.0, 0.0,
                  0.0,    0.0, 1.0, 0.0,
                 -1.0,    1.0, 0.0, 1.0,
            ];
            surface.make_current();
            let loc_tf = self.prog.get_uniform("tf").unwrap() as GLint;
            unsafe
            {
                gl::Viewport(0, 0, width as GLsizei, height as GLsizei);
                gl::UniformMatrix4fv(loc_tf, 1, gl::FALSE, tf.as_ptr());
            }
        }

        DrawContext::new(self, surface)
    }

    fn alloc_vert(&self)
    {
        self.vert_off.set(0);
        let size = self.max_verts * mem::size_of::<Vertex>();
        unsafe { gl::BufferData(gl::ARRAY_BUFFER, size as GLsizeiptr, ptr::null(), gl::STREAM_DRAW) };
    }

    fn alloc_idx(&self)
    {
        self.idx_off.set(0);
        let size = self.max_idxs * mem::size_of::<u16>();
        unsafe { gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, size as GLsizeiptr, ptr::null(), gl::STREAM_DRAW) };
    }

    fn clear(&self, r: f32, g: f32, b: f32, a: f32)
    {
        self.vert_len.set(0);
        self.idx_len.set(0);
        unsafe
        {
            gl::ClearColor(r, g, b, a);
            gl::Clear(gl::COLOR_BUFFER_BIT);
        }
    }

    fn push_elems<T: Array<u16>>(&self, ty: PrimType, tex: Option<GLuint>, verts: &[Vertex], idxs: T)
    {
        assert!(verts.len() <= self.max_verts && idxs.len() <= self.max_idxs);
        let tex = tex.unwrap_or(self.default_tex);

        let vert_start = self.vert_off.get() + self.vert_len.get();
        let idx_start = self.idx_off.get() + self.idx_len.get();

        let oom_vert = vert_start + verts.len() > self.max_verts;
        let oom_idx = idx_start + idxs.len() > self.max_idxs;
        let new_tex = self.cur_tex.get() != tex;

        if oom_vert || oom_idx || new_tex || self.cur_ty.get() != ty ||
            self.cur_ty.get() == PrimType::LineStrip  // close the previous line strip
        {
            self.commit();
            self.cur_ty.set(ty);

            if oom_vert { self.alloc_vert(); }
            if oom_idx { self.alloc_idx(); }

            if new_tex
            {
                self.cur_tex.set(tex);
                unsafe { gl::BindTexture(gl::TEXTURE_2D, tex) };
            }
        }

        let vert_size = vert_start * mem::size_of::<Vertex>();
        unsafe { gl::BufferSubData(gl::ARRAY_BUFFER, vert_size as GLsizeiptr, mem::size_of_val(verts) as GLintptr, verts.as_ptr() as *const _) };
        self.vert_len.set(self.vert_len.get() + verts.len());

        if !idxs.is_empty()
        {
            let idxs = idxs.map_(|idx| idx + vert_start as u16);
            let idx_size = idx_start * mem::size_of::<u16>();
            unsafe { gl::BufferSubData(gl::ELEMENT_ARRAY_BUFFER, idx_size as GLsizeiptr, mem::size_of_val(&idxs) as GLintptr, idxs.as_ptr() as *const _) };
            self.idx_len.set(self.idx_len.get() + idxs.len());
        }
    }

    fn commit(&self)
    {
        if self.vert_len.get() == 0 { return }

        match self.cur_ty.get()
        {
            PrimType::Triangles | PrimType::Lines => {
                let offset = self.idx_off.get() * mem::size_of::<i16>();
                unsafe { gl::DrawElements(self.cur_ty.get() as GLenum, self.idx_len.get() as GLsizei, gl::UNSIGNED_SHORT, offset as *const _) };
            }
            _ => {
                unsafe { gl::DrawArrays(self.cur_ty.get() as GLenum, self.vert_off.get() as GLint, self.vert_len.get() as GLsizei) };
            }
        }

        self.vert_off.set(self.vert_off.get() + self.vert_len.get());
        self.idx_off.set(self.idx_off.get() + self.idx_len.get());

        self.vert_len.set(0);
        self.idx_len.set(0);
    }
}

impl Drop for DrawEngine
{
    fn drop(&mut self)
    {
        unsafe
        {
            gl::DeleteBuffers(2, [self.vbo_vert, self.vbo_idx].as_ptr());
            gl::DeleteTextures(1, &self.default_tex);
        }
    }
}

pub type Point = [i16; 2];
pub type Color = [f32; 4];

pub struct DrawContext<'a>
{
    eng: &'a DrawEngine,
    surface: &'a Surface<'a>,
}

impl<'a> DrawContext<'a>
{
    fn new(eng: &'a DrawEngine, surface: &'a Surface) -> Self
    {
        DrawContext{
            eng: eng,
            surface: surface,
        }
    }

    pub fn clear(&self, color: Color)
    {
        self.eng.clear(color[0], color[1], color[2], color[3]);
    }

    pub fn draw_point(&self, pos: Point, color: Color)
    {
        self.eng.push_elems(PrimType::Points, None,
            &[Vertex{ pos: pos, col: color, texc: [0.0, 0.0] }], []);
    }

    pub fn draw_line(&self, p0: Point, p1: Point, color: Color)
    {
        self.eng.push_elems(PrimType::Lines, None, &[
            Vertex{ pos: p0, col: color, texc: [0.0, 0.0] },
            Vertex{ pos: p1, col: color, texc: [0.0, 0.0] },
        ], [0, 1]);
    }

    pub fn draw_polyline(&self, ps: &[Point], color: Color)
    {
        let verts: Vec<_> = ps.iter().map(|&p| Vertex{ pos: p, col: color, texc: [0.0, 0.0] }).collect();
        self.eng.push_elems(PrimType::LineStrip, None, &verts, []);
    }

    pub fn draw_triangle(&self, p0: Point, p1: Point, p2: Point, color: Color)
    {
        self.eng.push_elems(PrimType::Triangles, None, &[
            Vertex{ pos: p0, col: color, texc: [0.0, 0.0] },
            Vertex{ pos: p1, col: color, texc: [0.0, 0.0] },
            Vertex{ pos: p2, col: color, texc: [0.0, 0.0] },
        ], [0, 1, 2]);
    }

    pub fn draw_rect(&self, pos: Point, width: u16, height: u16, color: Color)
    {
        let (x, y) = (pos[0], pos[1]);
        let xw = x + width as i16;
        let yh = y + height as i16;

        self.eng.push_elems(PrimType::Triangles, None, &[
            Vertex{ pos: pos,      col: color, texc: [0.0, 0.0] },
            Vertex{ pos: [xw, y],  col: color, texc: [0.0, 0.0] },
            Vertex{ pos: [xw, yh], col: color, texc: [0.0, 0.0] },
            Vertex{ pos: [ x, yh], col: color, texc: [0.0, 0.0] },
        ], [
            0, 1, 2,
            2, 3, 0,
        ]);
    }
}

impl<'a> Drop for DrawContext<'a>
{
    fn drop(&mut self)
    {
        self.eng.commit();
        self.surface.swap_buffers();
    }
}
<commit_msg>DYNAMIC_DRAW reflects better the current buffer usage<commit_after>extern crate array_ext;
extern crate custom_gl as gl;
extern crate custom_egl as egl;

mod types;
#[macro_use]
mod typeinfo;
mod shader;
mod eglw;

use std::mem;
use std::ffi::CStr;
use std::ptr;
use std::cell::Cell;
use gl::types::*;
use typeinfo::TypeInfo;
use array_ext::Array;
use shader::{Shader, Program};

pub use egl::NativeDisplayType;
pub use egl::NativeWindowType;
pub use eglw::Surface;

#[derive(Debug, Clone, Copy)]
#[repr(C)]
struct Vertex
{
    pos: [i16; 2],
    col: [f32; 4],
    texc: [f32; 2],
}

impl_typeinfo!(Vertex, pos, col, texc);

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
enum PrimType
{
    Points = gl::POINTS,
    Lines = gl::LINES,
    LineStrip = gl::LINE_STRIP,
    Triangles = gl::TRIANGLES,
}

pub struct DrawEngine
{
    egl_disp: eglw::Display,
    prog: Program,
    vbo_vert: GLuint,
    vbo_idx: GLuint,
    default_tex: GLuint,
    cur_ty: Cell<PrimType>,
    vert_off: Cell<usize>,
    idx_off: Cell<usize>,
    vert_len: Cell<usize>,
    idx_len: Cell<usize>,
    cur_tex: Cell<GLuint>,
    pub max_verts: usize,
    pub max_idxs: usize,
}

impl DrawEngine
{
    pub fn new(xdisp: NativeDisplayType) -> Result<Self, &'static str>
    {
        let egl_disp = try!(eglw::Display::new(xdisp));

        unsafe
        {
            let vendor = CStr::from_ptr(gl::GetString(gl::VENDOR) as *const _);
            let renderer = CStr::from_ptr(gl::GetString(gl::RENDERER) as *const _);
            let version = CStr::from_ptr(gl::GetString(gl::VERSION) as *const _);
            let exts = CStr::from_ptr(gl::GetString(gl::EXTENSIONS) as *const _);
            println!("GL vendor: {:?}\nGL renderer: {:?}\nGL version: {:?}\nGL extensions: {:?}",
                vendor, renderer, version, exts);
        }

        unsafe
        {
            let prog = Program::new(&[
                Shader::new(gl::VERTEX_SHADER, &[include_str!("test.vert.glsl")]).unwrap_or_else(|e| panic!("vert: {}", e)),
                Shader::new(gl::FRAGMENT_SHADER, &[include_str!("test.frag.glsl")]).unwrap_or_else(|e| panic!("frag: {}", e)),
            ]).unwrap_or_else(|e| panic!("link: {}", e));
            prog.set_active();

            // vertex buffer
            let mut vbo_vert = 0;
            gl::GenBuffers(1, &mut vbo_vert);
            gl::BindBuffer(gl::ARRAY_BUFFER, vbo_vert);
            let size = mem::size_of::<Vertex>();
            Vertex::visit_fields(|name, offset, count, ty| {
                let num = prog.get_attrib(name).unwrap();
                gl::VertexAttribPointer(num, count as GLint, ty as GLenum, gl::FALSE, size as GLsizei, offset as *const _);
                gl::EnableVertexAttribArray(num);
            });

            // index buffer
            let mut vbo_idx = 0;
            gl::GenBuffers(1, &mut vbo_idx);
            gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, vbo_idx);

            // 1x1 white texture
            let mut tex = 0;
            gl::GenTextures(1, &mut tex);
            gl::BindTexture(gl::TEXTURE_2D, tex);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as GLint);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint);
            gl::TexImage2D(gl::TEXTURE_2D, 0, gl::LUMINANCE as GLint, 1, 1, 0, gl::LUMINANCE, gl::UNSIGNED_BYTE, [255u8].as_ptr() as *const _);

            let eng = DrawEngine{
                egl_disp: egl_disp,
                prog: prog,
                vbo_vert: vbo_vert,
                vbo_idx: vbo_idx,
                default_tex: tex,
                cur_ty: Cell::new(PrimType::Triangles),
                vert_off: Cell::new(0),
                idx_off: Cell::new(0),
                vert_len: Cell::new(0),
                idx_len: Cell::new(0),
                cur_tex: Cell::new(0),
                // allocate 1mb
                max_verts: 32768,
                max_idxs: 65536,
            };

            eng.alloc_vert();
            eng.alloc_idx();

            Ok(eng)
        }
    }

    pub fn create_window_surface(&self, win: NativeWindowType) -> Result<Surface, &'static str>
    {
        self.egl_disp.create_window_surface(win)
    }

    pub fn begin_draw<'a>(&'a self, surface: &'a Surface, (width, height): (u32, u32)) -> DrawContext
    {
        if !surface.is_current()
        {
            let (w, h) = (width as f32, height as f32);
            let tf = [
                2.0/w,    0.0, 0.0, 0.0,
                  0.0, -2.0/h, 0.0, 0.0,
                  0.0,    0.0, 1.0, 0.0,
                 -1.0,    1.0, 0.0, 1.0,
            ];
            surface.make_current();
            let loc_tf = self.prog.get_uniform("tf").unwrap() as GLint;
            unsafe
            {
                gl::Viewport(0, 0, width as GLsizei, height as GLsizei);
                gl::UniformMatrix4fv(loc_tf, 1, gl::FALSE, tf.as_ptr());
            }
        }

        DrawContext::new(self, surface)
    }

    fn alloc_vert(&self)
    {
        self.vert_off.set(0);
        let size = self.max_verts * mem::size_of::<Vertex>();
        unsafe { gl::BufferData(gl::ARRAY_BUFFER, size as GLsizeiptr, ptr::null(), gl::DYNAMIC_DRAW) };
    }

    fn alloc_idx(&self)
    {
        self.idx_off.set(0);
        let size = self.max_idxs * mem::size_of::<u16>();
        unsafe { gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, size as GLsizeiptr, ptr::null(), gl::DYNAMIC_DRAW) };
    }

    fn clear(&self, r: f32, g: f32, b: f32, a: f32)
    {
        self.vert_len.set(0);
        self.idx_len.set(0);
        unsafe
        {
            gl::ClearColor(r, g, b, a);
            gl::Clear(gl::COLOR_BUFFER_BIT);
        }
    }

    fn push_elems<T: Array<u16>>(&self, ty: PrimType, tex: Option<GLuint>, verts: &[Vertex], idxs: T)
    {
        assert!(verts.len() <= self.max_verts && idxs.len() <= self.max_idxs);
        let tex = tex.unwrap_or(self.default_tex);

        let vert_start = self.vert_off.get() + self.vert_len.get();
        let idx_start = self.idx_off.get() + self.idx_len.get();

        let oom_vert = vert_start + verts.len() > self.max_verts;
        let oom_idx = idx_start + idxs.len() > self.max_idxs;
        let new_tex = self.cur_tex.get() != tex;

        if oom_vert || oom_idx || new_tex || self.cur_ty.get() != ty ||
            self.cur_ty.get() == PrimType::LineStrip  // close the previous line strip
        {
            self.commit();
            self.cur_ty.set(ty);

            if oom_vert { self.alloc_vert(); }
            if oom_idx { self.alloc_idx(); }

            if new_tex
            {
                self.cur_tex.set(tex);
                unsafe { gl::BindTexture(gl::TEXTURE_2D, tex) };
            }
        }

        let vert_size = vert_start * mem::size_of::<Vertex>();
        unsafe { gl::BufferSubData(gl::ARRAY_BUFFER, vert_size as GLsizeiptr, mem::size_of_val(verts) as GLintptr, verts.as_ptr() as *const _) };
        self.vert_len.set(self.vert_len.get() + verts.len());

        if !idxs.is_empty()
        {
            let idxs = idxs.map_(|idx| idx + vert_start as u16);
            let idx_size = idx_start * mem::size_of::<u16>();
            unsafe { gl::BufferSubData(gl::ELEMENT_ARRAY_BUFFER, idx_size as GLsizeiptr, mem::size_of_val(&idxs) as GLintptr, idxs.as_ptr() as *const _) };
            self.idx_len.set(self.idx_len.get() + idxs.len());
        }
    }

    fn commit(&self)
    {
        if self.vert_len.get() == 0 { return }

        match self.cur_ty.get()
        {
            PrimType::Triangles | PrimType::Lines => {
                let offset = self.idx_off.get() * mem::size_of::<i16>();
                unsafe { gl::DrawElements(self.cur_ty.get() as GLenum, self.idx_len.get() as GLsizei, gl::UNSIGNED_SHORT, offset as *const _) };
            }
            _ => {
                unsafe { gl::DrawArrays(self.cur_ty.get() as GLenum, self.vert_off.get() as GLint, self.vert_len.get() as GLsizei) };
            }
        }

        self.vert_off.set(self.vert_off.get() + self.vert_len.get());
        self.idx_off.set(self.idx_off.get() + self.idx_len.get());

        self.vert_len.set(0);
        self.idx_len.set(0);
    }
}

impl Drop for DrawEngine
{
    fn drop(&mut self)
    {
        unsafe
        {
            gl::DeleteBuffers(2, [self.vbo_vert, self.vbo_idx].as_ptr());
            gl::DeleteTextures(1, &self.default_tex);
        }
    }
}

pub type Point = [i16; 2];
pub type Color = [f32; 4];

pub struct DrawContext<'a>
{
    eng: &'a DrawEngine,
    surface: &'a Surface<'a>,
}

impl<'a> DrawContext<'a>
{
    fn new(eng: &'a DrawEngine, surface: &'a Surface) -> Self
    {
        DrawContext{
            eng: eng,
            surface: surface,
        }
    }

    pub fn clear(&self, color: Color)
    {
        self.eng.clear(color[0], color[1], color[2], color[3]);
    }

    pub fn draw_point(&self, pos: Point, color: Color)
    {
        self.eng.push_elems(PrimType::Points, None,
            &[Vertex{ pos: pos, col: color, texc: [0.0, 0.0] }], []);
    }

    pub fn draw_line(&self, p0: Point, p1: Point, color: Color)
    {
        self.eng.push_elems(PrimType::Lines, None, &[
            Vertex{ pos: p0, col: color, texc: [0.0, 0.0] },
            Vertex{ pos: p1, col: color, texc: [0.0, 0.0] },
        ], [0, 1]);
    }

    pub fn draw_polyline(&self, ps: &[Point], color: Color)
    {
        let verts: Vec<_> = ps.iter().map(|&p| Vertex{ pos: p, col: color, texc: [0.0, 0.0] }).collect();
        self.eng.push_elems(PrimType::LineStrip, None, &verts, []);
    }

    pub fn draw_triangle(&self, p0: Point, p1: Point, p2: Point, color: Color)
    {
        self.eng.push_elems(PrimType::Triangles, None, &[
            Vertex{ pos: p0, col: color, texc: [0.0, 0.0] },
            Vertex{ pos: p1, col: color, texc: [0.0, 0.0] },
            Vertex{ pos: p2, col: color, texc: [0.0, 0.0] },
        ], [0, 1, 2]);
    }

    pub fn draw_rect(&self, pos: Point, width: u16, height: u16, color: Color)
    {
        let (x, y) = (pos[0], pos[1]);
        let xw = x + width as i16;
        let yh = y + height as i16;

        self.eng.push_elems(PrimType::Triangles, None, &[
            Vertex{ pos: pos,      col: color, texc: [0.0, 0.0] },
            Vertex{ pos: [xw, y],  col: color, texc: [0.0, 0.0] },
            Vertex{ pos: [xw, yh], col: color, texc: [0.0, 0.0] },
            Vertex{ pos: [ x, yh], col: color, texc: [0.0, 0.0] },
        ], [
            0, 1, 2,
            2, 3, 0,
        ]);
    }
}

impl<'a> Drop for DrawContext<'a>
{
    fn drop(&mut self)
    {
        self.eng.commit();
        self.surface.swap_buffers();
    }
}
<|endoftext|>


<commit_before>use std::{
    collections::BTreeMap,
    sync::{Arc, Mutex},
};

use futures::{future, Future, FutureExt as _};

use super::{non_cached, BatchFn, LoadError};

pub struct Loader<K, V, E, F, C>
where
    V: Clone,
    E: Clone,
    C: Cache<K, Result<V, LoadError<E>>>,
{
    loader: non_cached::Loader<K, V, E, F>,
    cache: Arc<Mutex<C>>,
}

impl<K, V, E, F, C> Clone for Loader<K, V, E, F, C>
where
    V: Clone,
    E: Clone,
    C: Cache<K, Result<V, LoadError<E>>>,
{
    fn clone(&self) -> Self {
        Self {
            loader: self.loader.clone(),
            cache: self.cache.clone(),
        }
    }
}

impl<K, V, E, F, C> Loader<K, V, E, F, C>
where
    K: Clone,
    V: Clone,
    E: Clone,
    C: Cache<K, Result<V, LoadError<E>>>,
{
    pub fn load(&self, key: K) -> impl Future<Output = Result<V, LoadError<E>>>
    where
        V: Unpin,
        F: BatchFn<K, V, Error = E>,
    {
        if let Some(res) = self.cache.lock().unwrap().get(&key) {
            return future::ready(res).left_future();
        }
        let cache = self.cache.clone();
        let loader = self.loader.clone();
        future::lazy(move |_| {
            if let Some(res) = cache.lock().unwrap().get(&key) {
                return future::ready(res).left_future();
            }
            loader
                .load(key.clone())
                .map(move |res| {
                    cache.lock().unwrap().insert(key, res.clone());
                    res
                })
                .right_future()
        })
        .flatten()
        .right_future()
    }

    pub fn load_many(&self, keys: Vec<K>) -> impl Future<Output = Result<Vec<V>, LoadError<E>>>
    where
        V: Unpin,
        F: BatchFn<K, V, Error = E>,
    {
        future::try_join_all(keys.into_iter().map(|v| self.load(v)))
    }

    pub fn remove(&self, key: &K) -> Option<Result<V, LoadError<E>>> {
        let mut cache = self.cache.lock().unwrap();
        cache.remove(key)
    }

    pub fn clear(&self) {
        let mut cache = self.cache.lock().unwrap();
        cache.clear();
    }

    pub fn prime(&self, key: K, val: V) {
        let mut cache = self.cache.lock().unwrap();
        if !cache.contains_key(&key) {
            cache.insert(key, Ok(val));
        }
    }
}

impl<K, V, E, F, C> Loader<K, V, E, F, C>
where
    K: Clone + Ord,
    V: Clone,
    E: Clone,
    C: Cache<K, Result<V, LoadError<E>>>,
{
    pub fn with_cache(loader: non_cached::Loader<K, V, E, F>, cache: C) -> Self {
        Loader {
            loader,
            cache: Arc::new(Mutex::new(cache)),
        }
    }
}

impl<K, V, E, F> Loader<K, V, E, F, BTreeMap<K, Result<V, LoadError<E>>>>
where
    K: Clone + Ord,
    V: Clone,
    E: Clone,
{
    pub fn new(loader: non_cached::Loader<K, V, E, F>) -> Self {
        Loader::with_cache(loader, BTreeMap::new())
    }
}

pub trait Cache<K, V> {
    fn contains_key(&self, key: &K) -> bool {
        self.get(key).is_some()
    }
    fn get(&self, key: &K) -> Option<V>;
    fn insert(&mut self, key: K, value: V);
    fn remove(&mut self, key: &K) -> Option<V>;
    fn clear(&mut self);
}

impl<K, V> Cache<K, V> for BTreeMap<K, V>
where
    K: Ord,
    V: Clone,
{
    fn contains_key(&self, key: &K) -> bool {
        BTreeMap::contains_key(self, key)
    }

    fn get(&self, key: &K) -> Option<V> {
        BTreeMap::get(self, key).map(|v| (*v).clone())
    }

    fn insert(&mut self, key: K, value: V) {
        BTreeMap::insert(self, key, value);
    }

    fn remove(&mut self, key: &K) -> Option<V> {
        BTreeMap::remove(self, key)
    }

    fn clear(&mut self) {
        BTreeMap::clear(self);
    }
}
<commit_msg>Fix cache regression<commit_after>use std::{
    collections::BTreeMap,
    sync::{Arc, Mutex},
};

use futures::{future, Future, FutureExt as _};

use super::{
    non_cached::{self, LoadFuture},
    BatchFn, LoadError,
};

pub struct Loader<K, V, E, F, C> {
    loader: non_cached::Loader<K, V, E, F>,
    cache: Arc<Mutex<C>>,
}

// Manual implementation is used to omit applying unnecessary Clone bounds.
impl<K, V, E, F, C> Clone for Loader<K, V, E, F, C> {
    fn clone(&self) -> Self {
        Self {
            loader: self.loader.clone(),
            cache: self.cache.clone(),
        }
    }
}

impl<K, V, E, F, C> Loader<K, V, E, F, C>
where
    K: Clone,
    V: Clone,
    E: Clone,
    F: BatchFn<K, V, Error = E>,
    C: Cache<K, Item<K, V, E, F>>,
{
    pub fn load(&self, key: K) -> impl Future<Output = Result<V, LoadError<E>>>
    where
        V: Unpin,
        F: BatchFn<K, V, Error = E>,
    {
        let mut cache = self.cache.lock().unwrap();
        if let Some(item) = cache.get(&key) {
            return item.into_future().left_future();
        }
        let item = CacheItem::Loading(self.loader.load(key.clone()).shared());
        cache.insert(key, item.clone());
        item.into_future().right_future()
    }

    pub fn load_many(&self, keys: Vec<K>) -> impl Future<Output = Result<Vec<V>, LoadError<E>>>
    where
        V: Unpin,
        F: BatchFn<K, V, Error = E>,
    {
        future::try_join_all(keys.into_iter().map(|v| self.load(v)))
    }

    pub fn remove(&self, key: &K) -> Option<impl Future<Output = Result<V, LoadError<E>>>> {
        let mut cache = self.cache.lock().unwrap();
        cache.remove(key).map(|item| item.into_future())
    }

    pub fn clear(&self) {
        let mut cache = self.cache.lock().unwrap();
        cache.clear();
    }

    pub fn prime(&self, key: K, val: V) {
        let mut cache = self.cache.lock().unwrap();
        if !cache.contains_key(&key) {
            cache.insert(key, CacheItem::Prime(Ok(val)));
        }
    }
}

impl<K, V, E, F, C> Loader<K, V, E, F, C> {
    pub fn with_cache(loader: non_cached::Loader<K, V, E, F>, cache: C) -> Self {
        Loader {
            loader,
            cache: Arc::new(Mutex::new(cache)),
        }
    }
}

impl<K, V, E, F> Loader<K, V, E, F, BTreeMap<K, Item<K, V, E, F>>>
where
    K: Ord,
    E: Clone,
    F: BatchFn<K, V, Error = E>,
{
    pub fn new(loader: non_cached::Loader<K, V, E, F>) -> Self {
        Loader::with_cache(loader, BTreeMap::new())
    }
}

pub type Item<K, V, E, F> = CacheItem<Result<V, LoadError<E>>, LoadFuture<K, V, E, F>>;

pub enum CacheItem<V, F: Future> {
    Prime(V),
    Loading(future::Shared<F>),
}

// Manual implementation is used to omit applying unnecessary Clone bounds.
impl<V, F> Clone for CacheItem<V, F>
where
    V: Clone,
    F: Future,
{
    fn clone(&self) -> Self {
        match self {
            CacheItem::Prime(ref v) => CacheItem::Prime(v.clone()),
            CacheItem::Loading(ref f) => CacheItem::Loading(f.clone()),
        }
    }
}

impl<V, F> CacheItem<V, F>
where
    V: Clone,
    F: Future<Output = V>,
{
    pub fn into_future(self) -> impl Future<Output = V> {
        match self {
            CacheItem::Prime(v) => future::ready(v).left_future(),
            CacheItem::Loading(f) => f.right_future(),
        }
    }
}

pub trait Cache<K, V> {
    fn contains_key(&self, key: &K) -> bool {
        self.get(key).is_some()
    }
    fn get(&self, key: &K) -> Option<V>;
    fn insert(&mut self, key: K, value: V);
    fn remove(&mut self, key: &K) -> Option<V>;
    fn clear(&mut self);
}

impl<K, V> Cache<K, V> for BTreeMap<K, V>
where
    K: Ord,
    V: Clone,
{
    fn contains_key(&self, key: &K) -> bool {
        BTreeMap::contains_key(self, key)
    }

    fn get(&self, key: &K) -> Option<V> {
        BTreeMap::get(self, key).map(|v| (*v).clone())
    }

    fn insert(&mut self, key: K, value: V) {
        BTreeMap::insert(self, key, value);
    }

    fn remove(&mut self, key: &K) -> Option<V> {
        BTreeMap::remove(self, key)
    }

    fn clear(&mut self) {
        BTreeMap::clear(self);
    }
}
<|endoftext|>


<commit_before>use dirs;
use libc;
use logger::{self, Level};
use std::fs::{self, DirBuilder};
use std::io;
use std::os::unix::fs::DirBuilderExt;
use std::path::{Path, PathBuf};

pub const NEWSBOAT_SUBDIR_XDG: &str = "newsboat";
pub const NEWSBOAT_CONFIG_SUBDIR: &str = ".newsboat";
pub const NEWSBEUTER_SUBDIR_XDG: &str = "newsbeuter";
pub const NEWSBEUTER_CONFIG_SUBDIR: &str = ".newsbeuter";
pub const LOCK_SUFFIX: &str = ".lock";

#[derive(Debug)]
pub struct ConfigPaths {
    m_env_home: PathBuf,
    m_error_message: String,

    m_data_dir: PathBuf,
    m_config_dir: PathBuf,

    m_url_file: String,
    m_cache_file: String,
    m_config_file: String,
    m_lock_file: String,
    m_queue_file: String,
    m_search_file: String,
    m_cmdline_file: String,

    m_silent: bool,
    m_using_nonstandard_configs: bool,
}

impl ConfigPaths {
    pub fn new() -> ConfigPaths {
        let mut config_paths = ConfigPaths {
            m_env_home: PathBuf::new(),
            m_error_message: String::new(),

            m_data_dir: PathBuf::new(),
            m_config_dir: PathBuf::new(),

            m_url_file: String::from("urls"),
            m_cache_file: String::from("cache.db"),
            m_config_file: String::from("config"),
            m_lock_file: String::new(),
            m_queue_file: String::from("queue"),
            m_search_file: String::new(),
            m_cmdline_file: String::new(),

            m_silent: false,
            m_using_nonstandard_configs: false,
        };

        let m_env_home = dirs::home_dir();
        if m_env_home.is_none() {
            let uid = unsafe { libc::getuid() };

            // TODO: i18n
            config_paths.m_error_message = format!("Fatal error: couldn't determine home directory!\nPlease set the HOME environment variable or add a valid user for UID {}!", uid);

            return config_paths;
        }

        // hitting this branch means we found a home directory
        // we can now safely call unwrap on all other functions in the dirs crate

        config_paths.m_env_home = m_env_home.unwrap();
        config_paths.find_dirs();
        config_paths
    }

    fn migrate_data_from_newsbeuter_xdg(&mut self) -> bool {
        // This can't panic because we've tested we can find the home directory in ConfigPaths::new
        // This should be replaced with proper error handling after this is not used by c++ anymore
        let xdg_config_dir = dirs::config_dir().unwrap();
        let xdg_data_dir = dirs::data_dir().unwrap();

        let newsbeuter_config_dir = xdg_config_dir.join(NEWSBEUTER_SUBDIR_XDG);
        let newsbeuter_data_dir = xdg_data_dir.join(NEWSBEUTER_SUBDIR_XDG);

        let newsboat_config_dir = xdg_config_dir.join(NEWSBOAT_SUBDIR_XDG);
        let newsboat_data_dir = xdg_data_dir.join(NEWSBOAT_SUBDIR_XDG);

        if !newsbeuter_config_dir.is_dir() {
            return false;
        }

        fn exists(path: &Path) -> bool {
            let exists = path.exists();
            if exists {
                log!(
                    Level::Debug,
                    "{:?} already exists, aborting XDG migration.",
                    path
                );
            }
            return exists;
        }

        if exists(&newsboat_config_dir) {
            return false;
        }

        if exists(&newsboat_data_dir) {
            return false;
        }

        self.m_config_dir = newsboat_config_dir;
        self.m_data_dir = newsboat_data_dir;

        if !self.m_silent {
            eprintln!("Migrating configs and data from Newsbeuter's XDG dirs...");
        }

        if !try_mkdir(&self.m_config_dir) {
            return false;
        }

        if !try_mkdir(&self.m_data_dir) {
            return false;
        }

        // in config
        migrate_file(&newsbeuter_config_dir, &self.m_config_dir, "urls").is_ok();
        migrate_file(&newsbeuter_config_dir, &self.m_config_dir, "config").is_ok();

        // in data
        migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "cache.db").is_ok();
        migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "queue").is_ok();
        migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "history.search").is_ok();
        migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "history.cmdline").is_ok();

        return true;
    }

    fn migrate_data_from_newsbeuter_simple(&self) -> bool {
        let newsbeuter_dir = self.m_env_home.join(NEWSBEUTER_CONFIG_SUBDIR);

        if !newsbeuter_dir.is_dir() {
            return false;
        }

        let newsboat_dir = self.m_env_home.join(NEWSBOAT_CONFIG_SUBDIR);

        if newsboat_dir.exists() {
            log!(
                Level::Debug,
                "{:?} already exists, aborting migration.",
                newsboat_dir
            );
            return false;
        }

        if !self.m_silent {
            eprintln!("Migrating configs and data from Newsbeuter's dotdir...");
        }

        match mkdir(&newsboat_dir, 0o700) {
            Ok(_) => (),
            Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => (),
            Err(err) => {
                if !self.m_silent {
                    eprintln!(
                        "Aborting migration because mkdir on {:?} failed: {}",
                        newsboat_dir, err
                    );
                }
                return false;
            }
        };

        migrate_file(&newsbeuter_dir, &newsboat_dir, "urls").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "cache.db").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "config").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "queue").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "history.search").ok();
        migrate_file(&newsbeuter_dir, &newsboat_dir, "history.cmdline").ok();

        return true;
    }

    fn migrate_data_from_newsbeuter(&mut self) -> bool {
        let mut migrated = self.migrate_data_from_newsbeuter_xdg();

        if migrated {
            // Re-running to pick up XDG dirs
            self.m_url_file = "urls".into();
            self.m_cache_file = "cache.db".into();
            self.m_config_file = "config".into();
            self.m_queue_file = "queue".into();
            self.find_dirs();
        } else {
            migrated = self.migrate_data_from_newsbeuter_simple();
        }

        migrated
    }

    fn create_dirs(&self) -> bool {
        return try_mkdir(&self.m_config_dir) && try_mkdir(&self.m_data_dir);
    }

    fn find_dirs(&mut self) {
        self.m_config_dir = self.m_env_home.join(NEWSBOAT_CONFIG_SUBDIR);

        self.m_data_dir = self.m_config_dir.clone();

        // Will change config_dir and data_dir to point to XDG if XDG
        // directories are available.
        self.find_dirs_xdg();

        // in config
        self.m_url_file = self
            .m_config_dir
            .join(&self.m_url_file)
            .to_string_lossy()
            .into();
        self.m_config_file = self
            .m_config_dir
            .join(&self.m_config_file)
            .to_string_lossy()
            .into();

        // in data
        self.m_cache_file = self
            .m_data_dir
            .join(&self.m_cache_file)
            .to_string_lossy()
            .into();
        self.m_lock_file = self.m_cache_file.clone() + LOCK_SUFFIX;
        self.m_queue_file = self
            .m_data_dir
            .join(&self.m_queue_file)
            .to_string_lossy()
            .into();
        self.m_search_file = self
            .m_data_dir
            .join("history.search")
            .to_string_lossy()
            .into();
        self.m_cmdline_file = self
            .m_data_dir
            .join("history.cmdline")
            .to_string_lossy()
            .into();
    }

    fn find_dirs_xdg(&mut self) {
        // This can't panic because we've tested we can find the home directory in ConfigPaths::new
        // This should be replaced with proper error handling after this is not used by c++ anymore
        let config_dir = dirs::config_dir().unwrap().join(NEWSBOAT_SUBDIR_XDG);
        let data_dir = dirs::data_dir().unwrap().join(NEWSBOAT_SUBDIR_XDG);

        if !config_dir.is_dir() {
            return;
        }

        /* Invariant: config dir exists.
         *
         * At this point, we're confident we'll be using XDG. We don't check if
         * data dir exists, because if it doesn't we'll create it. */

        self.m_config_dir = config_dir;
        self.m_data_dir = data_dir;
    }

    /// Indicates if the object can be used.
    ///
    /// If this method returned `false`, the cause for initialization failure can be found using
    /// `error_message()`.
    pub fn initialized(&self) -> bool {
        !self.m_env_home.as_os_str().is_empty()
    }

    /// Returns explanation why initialization failed.
    ///
    /// \note You shouldn't call this unless `initialized()` returns `false`.
    pub fn error_message(&self) -> &str {
        &self.m_error_message
    }

    // TODO
    /*
    /// Initializes paths to config, cache etc. from CLI arguments.
    pub fn process_args(const CliArgsParser& args) {
        unimplemented!()
    }
    */

    /// Migrate configs and data from Newsbeuter if they exist. Return `true` if migrated
    /// something, `false` otherwise.
    pub fn try_migrate_from_newsbeuter(&mut self) -> bool {
        if !self.m_using_nonstandard_configs && !Path::new(&self.m_url_file).exists() {
            return self.migrate_data_from_newsbeuter();
        }

        // No migration occurred.
        false
    }

    /// Path to the URLs file.
    pub fn url_file(&self) -> String {
        self.m_url_file.clone()
    }

    /// Path to the cache file.
    pub fn cache_file(&self) -> String {
        self.m_cache_file.clone()
    }

    /// Sets path to the cache file.
    // FIXME: this is actually a kludge that lets Controller change the path
    // midway. That logic should be moved into ConfigPaths, and this method
    // removed.
    pub fn set_cache_file(&mut self, path: String) {
        self.m_cache_file = path.clone();
        self.m_lock_file = path + LOCK_SUFFIX;
    }

    /// Path to the config file.
    pub fn config_file(&self) -> String {
        self.m_config_file.clone()
    }

    /// Path to the lock file.
    ///
    /// \note This changes when path to config file changes.
    pub fn lock_file(&self) -> String {
        self.m_lock_file.clone()
    }

    /// \brief Path to the queue file.
    ///
    /// Queue file stores enqueued podcasts. It's written by Newsboat, and read by Podboat.
    pub fn queue_file(&self) -> String {
        self.m_queue_file.clone()
    }

    /// Path to the file with previous search queries.
    pub fn search_file(&self) -> String {
        self.m_search_file.clone()
    }

    /// Path to the file with command-line history.
    pub fn cmdline_file(&self) -> String {
        self.m_cmdline_file.clone()
    }
}

fn try_mkdir<R: AsRef<Path>>(path: R) -> bool {
    DirBuilder::new()
        .recursive(true)
        .mode(0o700)
        .create(path.as_ref())
        .is_ok()
}

fn mkdir<R: AsRef<Path>>(path: R, mode: u32) -> io::Result<()> {
    DirBuilder::new().mode(mode).create(path.as_ref())
}

fn migrate_file<R: AsRef<Path>>(newsbeuter_dir: R, newsboat_dir: R, file: &str) -> io::Result<()> {
    let input_filepath = newsbeuter_dir.as_ref().join(file);
    let output_filepath = newsboat_dir.as_ref().join(file);
    eprintln!("{:?} -> {:?}", input_filepath, output_filepath);
    fs::copy(input_filepath, output_filepath).map(|_| ())
}
<commit_msg>Explain why we don't check return value of `migrate_file`<commit_after>use dirs;
use libc;
use logger::{self, Level};
use std::fs::{self, DirBuilder};
use std::io;
use std::os::unix::fs::DirBuilderExt;
use std::path::{Path, PathBuf};

pub const NEWSBOAT_SUBDIR_XDG: &str = "newsboat";
pub const NEWSBOAT_CONFIG_SUBDIR: &str = ".newsboat";
pub const NEWSBEUTER_SUBDIR_XDG: &str = "newsbeuter";
pub const NEWSBEUTER_CONFIG_SUBDIR: &str = ".newsbeuter";
pub const LOCK_SUFFIX: &str = ".lock";

#[derive(Debug)]
pub struct ConfigPaths {
    m_env_home: PathBuf,
    m_error_message: String,

    m_data_dir: PathBuf,
    m_config_dir: PathBuf,

    m_url_file: String,
    m_cache_file: String,
    m_config_file: String,
    m_lock_file: String,
    m_queue_file: String,
    m_search_file: String,
    m_cmdline_file: String,

    m_silent: bool,
    m_using_nonstandard_configs: bool,
}

impl ConfigPaths {
    pub fn new() -> ConfigPaths {
        let mut config_paths = ConfigPaths {
            m_env_home: PathBuf::new(),
            m_error_message: String::new(),

            m_data_dir: PathBuf::new(),
            m_config_dir: PathBuf::new(),

            m_url_file: String::from("urls"),
            m_cache_file: String::from("cache.db"),
            m_config_file: String::from("config"),
            m_lock_file: String::new(),
            m_queue_file: String::from("queue"),
            m_search_file: String::new(),
            m_cmdline_file: String::new(),

            m_silent: false,
            m_using_nonstandard_configs: false,
        };

        let m_env_home = dirs::home_dir();
        if m_env_home.is_none() {
            let uid = unsafe { libc::getuid() };

            // TODO: i18n
            config_paths.m_error_message = format!("Fatal error: couldn't determine home directory!\nPlease set the HOME environment variable or add a valid user for UID {}!", uid);

            return config_paths;
        }

        // hitting this branch means we found a home directory
        // we can now safely call unwrap on all other functions in the dirs crate

        config_paths.m_env_home = m_env_home.unwrap();
        config_paths.find_dirs();
        config_paths
    }

    fn migrate_data_from_newsbeuter_xdg(&mut self) -> bool {
        // This can't panic because we've tested we can find the home directory in ConfigPaths::new
        // This should be replaced with proper error handling after this is not used by c++ anymore
        let xdg_config_dir = dirs::config_dir().unwrap();
        let xdg_data_dir = dirs::data_dir().unwrap();

        let newsbeuter_config_dir = xdg_config_dir.join(NEWSBEUTER_SUBDIR_XDG);
        let newsbeuter_data_dir = xdg_data_dir.join(NEWSBEUTER_SUBDIR_XDG);

        let newsboat_config_dir = xdg_config_dir.join(NEWSBOAT_SUBDIR_XDG);
        let newsboat_data_dir = xdg_data_dir.join(NEWSBOAT_SUBDIR_XDG);

        if !newsbeuter_config_dir.is_dir() {
            return false;
        }

        fn exists(path: &Path) -> bool {
            let exists = path.exists();
            if exists {
                log!(
                    Level::Debug,
                    "{:?} already exists, aborting XDG migration.",
                    path
                );
            }
            return exists;
        }

        if exists(&newsboat_config_dir) {
            return false;
        }

        if exists(&newsboat_data_dir) {
            return false;
        }

        self.m_config_dir = newsboat_config_dir;
        self.m_data_dir = newsboat_data_dir;

        if !self.m_silent {
            eprintln!("Migrating configs and data from Newsbeuter's XDG dirs...");
        }

        if !try_mkdir(&self.m_config_dir) {
            return false;
        }

        if !try_mkdir(&self.m_data_dir) {
            return false;
        }

        // We ignore the return codes because it's okay if some files are missing.

        // in config
        let _ = migrate_file(&newsbeuter_config_dir, &self.m_config_dir, "urls");
        let _ = migrate_file(&newsbeuter_config_dir, &self.m_config_dir, "config");

        // in data
        let _ = migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "cache.db");
        let _ = migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "queue");
        let _ = migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "history.search");
        let _ = migrate_file(&newsbeuter_data_dir, &self.m_data_dir, "history.cmdline");

        return true;
    }

    fn migrate_data_from_newsbeuter_simple(&self) -> bool {
        let newsbeuter_dir = self.m_env_home.join(NEWSBEUTER_CONFIG_SUBDIR);

        if !newsbeuter_dir.is_dir() {
            return false;
        }

        let newsboat_dir = self.m_env_home.join(NEWSBOAT_CONFIG_SUBDIR);

        if newsboat_dir.exists() {
            log!(
                Level::Debug,
                "{:?} already exists, aborting migration.",
                newsboat_dir
            );
            return false;
        }

        if !self.m_silent {
            eprintln!("Migrating configs and data from Newsbeuter's dotdir...");
        }

        match mkdir(&newsboat_dir, 0o700) {
            Ok(_) => (),
            Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => (),
            Err(err) => {
                if !self.m_silent {
                    eprintln!(
                        "Aborting migration because mkdir on {:?} failed: {}",
                        newsboat_dir, err
                    );
                }
                return false;
            }
        };

        // We ignore the return codes because it's okay if some files are missing.
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, "urls");
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, "cache.db");
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, "config");
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, "queue");
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, "history.search");
        let _ = migrate_file(&newsbeuter_dir, &newsboat_dir, "history.cmdline");

        return true;
    }

    fn migrate_data_from_newsbeuter(&mut self) -> bool {
        let mut migrated = self.migrate_data_from_newsbeuter_xdg();

        if migrated {
            // Re-running to pick up XDG dirs
            self.m_url_file = "urls".into();
            self.m_cache_file = "cache.db".into();
            self.m_config_file = "config".into();
            self.m_queue_file = "queue".into();
            self.find_dirs();
        } else {
            migrated = self.migrate_data_from_newsbeuter_simple();
        }

        migrated
    }

    fn create_dirs(&self) -> bool {
        return try_mkdir(&self.m_config_dir) && try_mkdir(&self.m_data_dir);
    }

    fn find_dirs(&mut self) {
        self.m_config_dir = self.m_env_home.join(NEWSBOAT_CONFIG_SUBDIR);

        self.m_data_dir = self.m_config_dir.clone();

        // Will change config_dir and data_dir to point to XDG if XDG
        // directories are available.
        self.find_dirs_xdg();

        // in config
        self.m_url_file = self
            .m_config_dir
            .join(&self.m_url_file)
            .to_string_lossy()
            .into();
        self.m_config_file = self
            .m_config_dir
            .join(&self.m_config_file)
            .to_string_lossy()
            .into();

        // in data
        self.m_cache_file = self
            .m_data_dir
            .join(&self.m_cache_file)
            .to_string_lossy()
            .into();
        self.m_lock_file = self.m_cache_file.clone() + LOCK_SUFFIX;
        self.m_queue_file = self
            .m_data_dir
            .join(&self.m_queue_file)
            .to_string_lossy()
            .into();
        self.m_search_file = self
            .m_data_dir
            .join("history.search")
            .to_string_lossy()
            .into();
        self.m_cmdline_file = self
            .m_data_dir
            .join("history.cmdline")
            .to_string_lossy()
            .into();
    }

    fn find_dirs_xdg(&mut self) {
        // This can't panic because we've tested we can find the home directory in ConfigPaths::new
        // This should be replaced with proper error handling after this is not used by c++ anymore
        let config_dir = dirs::config_dir().unwrap().join(NEWSBOAT_SUBDIR_XDG);
        let data_dir = dirs::data_dir().unwrap().join(NEWSBOAT_SUBDIR_XDG);

        if !config_dir.is_dir() {
            return;
        }

        /* Invariant: config dir exists.
         *
         * At this point, we're confident we'll be using XDG. We don't check if
         * data dir exists, because if it doesn't we'll create it. */

        self.m_config_dir = config_dir;
        self.m_data_dir = data_dir;
    }

    /// Indicates if the object can be used.
    ///
    /// If this method returned `false`, the cause for initialization failure can be found using
    /// `error_message()`.
    pub fn initialized(&self) -> bool {
        !self.m_env_home.as_os_str().is_empty()
    }

    /// Returns explanation why initialization failed.
    ///
    /// \note You shouldn't call this unless `initialized()` returns `false`.
    pub fn error_message(&self) -> &str {
        &self.m_error_message
    }

    // TODO
    /*
    /// Initializes paths to config, cache etc. from CLI arguments.
    pub fn process_args(const CliArgsParser& args) {
        unimplemented!()
    }
    */

    /// Migrate configs and data from Newsbeuter if they exist. Return `true` if migrated
    /// something, `false` otherwise.
    pub fn try_migrate_from_newsbeuter(&mut self) -> bool {
        if !self.m_using_nonstandard_configs && !Path::new(&self.m_url_file).exists() {
            return self.migrate_data_from_newsbeuter();
        }

        // No migration occurred.
        false
    }

    /// Path to the URLs file.
    pub fn url_file(&self) -> String {
        self.m_url_file.clone()
    }

    /// Path to the cache file.
    pub fn cache_file(&self) -> String {
        self.m_cache_file.clone()
    }

    /// Sets path to the cache file.
    // FIXME: this is actually a kludge that lets Controller change the path
    // midway. That logic should be moved into ConfigPaths, and this method
    // removed.
    pub fn set_cache_file(&mut self, path: String) {
        self.m_cache_file = path.clone();
        self.m_lock_file = path + LOCK_SUFFIX;
    }

    /// Path to the config file.
    pub fn config_file(&self) -> String {
        self.m_config_file.clone()
    }

    /// Path to the lock file.
    ///
    /// \note This changes when path to config file changes.
    pub fn lock_file(&self) -> String {
        self.m_lock_file.clone()
    }

    /// \brief Path to the queue file.
    ///
    /// Queue file stores enqueued podcasts. It's written by Newsboat, and read by Podboat.
    pub fn queue_file(&self) -> String {
        self.m_queue_file.clone()
    }

    /// Path to the file with previous search queries.
    pub fn search_file(&self) -> String {
        self.m_search_file.clone()
    }

    /// Path to the file with command-line history.
    pub fn cmdline_file(&self) -> String {
        self.m_cmdline_file.clone()
    }
}

fn try_mkdir<R: AsRef<Path>>(path: R) -> bool {
    DirBuilder::new()
        .recursive(true)
        .mode(0o700)
        .create(path.as_ref())
        .is_ok()
}

fn mkdir<R: AsRef<Path>>(path: R, mode: u32) -> io::Result<()> {
    DirBuilder::new().mode(mode).create(path.as_ref())
}

fn migrate_file<R: AsRef<Path>>(newsbeuter_dir: R, newsboat_dir: R, file: &str) -> io::Result<()> {
    let input_filepath = newsbeuter_dir.as_ref().join(file);
    let output_filepath = newsboat_dir.as_ref().join(file);
    eprintln!("{:?} -> {:?}", input_filepath, output_filepath);
    fs::copy(input_filepath, output_filepath).map(|_| ())
}
<|endoftext|>


<commit_before>// This example drives c-ares using an mio::EventLoop.
extern crate c_ares;
extern crate mio;

use std::collections::HashSet;
use std::mem;
use std::os::unix::io;
use std::sync::mpsc;
use std::thread;

// Messages for the event loop.
enum CAresHandlerMessage {
    // 'Notify me when this file descriptor becomes readable, or writable'
    // The first bool is for 'readable' and the second is for 'writable'.  It's
    // allowed to set both of these - or neither, meaning 'I am no longer
    // interested in this file descriptor'.
    RegisterInterest(io::RawFd, bool, bool),

    // 'Shut down'.
    ShutDown,
}

struct CAresEventHandler {
    // Since the event handler owns the Channel, it's tricky to submit further
    // queries once the event loop is running.  If you want to do that,
    // either:
    //
    // -  share the Channel by using an Arc<Mutex<c_ares::Channel>>, OR
    // -  send requests to the event handler as messages, and have it make the
    //    queries
    ares_channel: c_ares::Channel,
    tracked_fds: HashSet<io::RawFd>,
}

impl mio::Handler for CAresEventHandler {
    type Timeout = ();
    type Message = CAresHandlerMessage;

    // mio notifies us that a file descriptor is readable or writable, so we
    // tell the Channel the same.
    fn ready(
        &mut self,
        _event_loop: &mut mio::EventLoop<CAresEventHandler>,
        token: mio::Token,
        events: mio::EventSet) {
        let fd = token.as_usize() as io::RawFd;
        let read_fd = if events.is_readable() { fd } else { c_ares::INVALID_FD };
        let write_fd = if events.is_writable() { fd } else { c_ares::INVALID_FD };
        self.ares_channel.process_fd(read_fd, write_fd);
    }

    // Process received messages.  Either:
    // - we're asked to register interest (or non-interest) in a file
    // descriptor
    // - we're asked to shut down the event loop.
    fn notify(
        &mut self,
        event_loop:&mut mio::EventLoop<CAresEventHandler>,
        msg: Self::Message) {
        match msg {
            CAresHandlerMessage::RegisterInterest(fd, readable, writable) => {
                let io = mio::Io::from(fd);
                if !readable && !writable {
                    self.tracked_fds.remove(&fd);
                    event_loop
                        .deregister(&io)
                        .ok()
                        .expect("failed to deregister interest");
                } else {
                    let mut interest = mio::EventSet::none();
                    if readable {
                        interest = interest | mio::EventSet::readable();
                    }
                    if writable {
                        interest = interest | mio::EventSet::writable();
                    }
                    let token = mio::Token(fd as usize);
                    let register_result = if !self.tracked_fds.insert(fd) {
                        event_loop
                            .reregister(
                                &io,
                                token,
                                interest,
                                mio::PollOpt::level())
                    } else {
                        event_loop
                            .register_opt(
                                &io,
                                token,
                                interest,
                                mio::PollOpt::level())
                    };
                    register_result.ok().expect("failed to register interest");
                }

                // Don't accidentally close the file descriptor by dropping io!
                mem::forget(io);
            },

            CAresHandlerMessage::ShutDown => event_loop.shutdown(),
        }
    }

    // We run a recurring timer so that we can spot non-responsive servers.
    //
    // In that case we won't get a callback saying that anything is happening
    // on any file descriptor, but nevertheless need to give the Channel an
    // opportunity to notice that it has timed-out requests pending.
    fn timeout(
        &mut self,
        event_loop: &mut mio::EventLoop<CAresEventHandler>,
        _timeout: Self::Timeout) {
        self.ares_channel.process_fd(c_ares::INVALID_FD, c_ares::INVALID_FD);
        event_loop.timeout_ms((), 500).unwrap();
    }
}

impl CAresEventHandler {
    fn new(ares_channel: c_ares::Channel) -> CAresEventHandler {
        CAresEventHandler {
            ares_channel: ares_channel,
            tracked_fds: HashSet::new(),
        }
    }
}

fn print_a_results(result: Result<c_ares::AResults, c_ares::AresError>) {
    println!("");
    match result {
        Err(e) => {
            let err_string = c_ares::str_error(e);
            println!("A lookup failed with error '{:}'", err_string);
        }
        Ok(a_results) => {
            println!("Successful A lookup...");
            println!("Hostname: {}", a_results.hostname());
            for addr in a_results.iter() {
                println!("{:}", addr);
            }
        }
    }
}

fn print_aaaa_results(result: Result<c_ares::AAAAResults, c_ares::AresError>) {
    println!("");
    match result {
        Err(e) => {
            let err_string = c_ares::str_error(e);
            println!("AAAA lookup failed with error '{:}'", err_string);
        }
        Ok(aaaa_results) => {
            println!("Successful AAAA lookup...");
            println!("Hostname: {}", aaaa_results.hostname());
            for addr in aaaa_results.iter() {
                println!("{:}", addr);
            }
        }
    }
}

fn print_cname_result(result: Result<c_ares::CNameResult, c_ares::AresError>) {
    println!("");
    match result {
        Err(e) => {
            let err_string = c_ares::str_error(e);
            println!("CNAME lookup failed with error '{:}'", err_string);
        }
        Ok(cname_result) => {
            println!("Successful CNAME lookup...");
            println!("{}", cname_result.cname());
        }
    }
}

fn print_mx_results(result: Result<c_ares::MXResults, c_ares::AresError>) {
    println!("");
    match result {
        Err(e) => {
            let err_string = c_ares::str_error(e);
            println!("AAAA lookup failed with error '{:}'", err_string);
        }
        Ok(mx_results) => {
            println!("Successful MX lookup...");
            for mx_result in mx_results.iter() {
                println!(
                    "host {}, priority {}",
                    mx_result.host(),
                    mx_result.priority());
            }
        }
    }
}

fn main() {
    // Create an event loop, and a c_ares::Channel.
    let mut event_loop = mio::EventLoop::new()
        .ok()
        .expect("failed to create event loop");
    let event_loop_channel = event_loop.channel();
    let event_loop_channel_clone = event_loop_channel.clone();
    let sock_callback = move |fd: io::RawFd, readable: bool, writable: bool| {
        event_loop_channel_clone
            .send(CAresHandlerMessage::RegisterInterest(fd, readable, writable))
            .ok()
            .expect("Failed to send RegisterInterest");
    };
    let mut options = c_ares::Options::new();
    options
        .set_flags(c_ares::flags::STAYOPEN | c_ares::flags::EDNS)
        .set_timeout(500)
        .set_tries(3);
    let mut ares_channel = c_ares::Channel::new(sock_callback, options)
        .ok()
        .expect("Failed to create channel");

    // Set up a couple of queries.
    let (results_tx, results_rx) = mpsc::channel();
    let tx = results_tx.clone();
    ares_channel.query_a("apple.com", move |results| {
        print_a_results(results);
        tx.send(()).unwrap()
    });

    let tx = results_tx.clone();
    ares_channel.query_aaaa("google.com", move |results| {
        print_aaaa_results(results);
        tx.send(()).unwrap()
    });

    let tx = results_tx.clone();
    ares_channel.query_cname("dimbleby.github.io", move |result| {
        print_cname_result(result);
        tx.send(()).unwrap()
    });

    let tx = results_tx.clone();
    ares_channel.query_mx("gmail.com", move |results| {
        print_mx_results(results);
        tx.send(()).unwrap()
    });

    // Set the first instance of the recurring timer on the event loop.
    event_loop.timeout_ms((), 500).unwrap();

    // Kick off the event loop.
    let mut event_handler = CAresEventHandler::new(ares_channel);
    let handle = thread::spawn(move || {
        event_loop
            .run(&mut event_handler)
            .ok()
            .expect("failed to run event loop")
    });

    // Wait for results to roll in.
    for _ in 0..4 {
        results_rx.recv().unwrap();
    }

    // Shut down the event loop and wait for it to finish.
    event_loop_channel
        .send(CAresHandlerMessage::ShutDown)
        .ok()
        .expect("failed to shut down event loop");
    handle.join().unwrap();
}
<commit_msg>Copy/paste error<commit_after>// This example drives c-ares using an mio::EventLoop.
extern crate c_ares;
extern crate mio;

use std::collections::HashSet;
use std::mem;
use std::os::unix::io;
use std::sync::mpsc;
use std::thread;

// Messages for the event loop.
enum CAresHandlerMessage {
    // 'Notify me when this file descriptor becomes readable, or writable'
    // The first bool is for 'readable' and the second is for 'writable'.  It's
    // allowed to set both of these - or neither, meaning 'I am no longer
    // interested in this file descriptor'.
    RegisterInterest(io::RawFd, bool, bool),

    // 'Shut down'.
    ShutDown,
}

struct CAresEventHandler {
    // Since the event handler owns the Channel, it's tricky to submit further
    // queries once the event loop is running.  If you want to do that,
    // either:
    //
    // -  share the Channel by using an Arc<Mutex<c_ares::Channel>>, OR
    // -  send requests to the event handler as messages, and have it make the
    //    queries
    ares_channel: c_ares::Channel,
    tracked_fds: HashSet<io::RawFd>,
}

impl mio::Handler for CAresEventHandler {
    type Timeout = ();
    type Message = CAresHandlerMessage;

    // mio notifies us that a file descriptor is readable or writable, so we
    // tell the Channel the same.
    fn ready(
        &mut self,
        _event_loop: &mut mio::EventLoop<CAresEventHandler>,
        token: mio::Token,
        events: mio::EventSet) {
        let fd = token.as_usize() as io::RawFd;
        let read_fd = if events.is_readable() { fd } else { c_ares::INVALID_FD };
        let write_fd = if events.is_writable() { fd } else { c_ares::INVALID_FD };
        self.ares_channel.process_fd(read_fd, write_fd);
    }

    // Process received messages.  Either:
    // - we're asked to register interest (or non-interest) in a file
    // descriptor
    // - we're asked to shut down the event loop.
    fn notify(
        &mut self,
        event_loop:&mut mio::EventLoop<CAresEventHandler>,
        msg: Self::Message) {
        match msg {
            CAresHandlerMessage::RegisterInterest(fd, readable, writable) => {
                let io = mio::Io::from(fd);
                if !readable && !writable {
                    self.tracked_fds.remove(&fd);
                    event_loop
                        .deregister(&io)
                        .ok()
                        .expect("failed to deregister interest");
                } else {
                    let mut interest = mio::EventSet::none();
                    if readable {
                        interest = interest | mio::EventSet::readable();
                    }
                    if writable {
                        interest = interest | mio::EventSet::writable();
                    }
                    let token = mio::Token(fd as usize);
                    let register_result = if !self.tracked_fds.insert(fd) {
                        event_loop
                            .reregister(
                                &io,
                                token,
                                interest,
                                mio::PollOpt::level())
                    } else {
                        event_loop
                            .register_opt(
                                &io,
                                token,
                                interest,
                                mio::PollOpt::level())
                    };
                    register_result.ok().expect("failed to register interest");
                }

                // Don't accidentally close the file descriptor by dropping io!
                mem::forget(io);
            },

            CAresHandlerMessage::ShutDown => event_loop.shutdown(),
        }
    }

    // We run a recurring timer so that we can spot non-responsive servers.
    //
    // In that case we won't get a callback saying that anything is happening
    // on any file descriptor, but nevertheless need to give the Channel an
    // opportunity to notice that it has timed-out requests pending.
    fn timeout(
        &mut self,
        event_loop: &mut mio::EventLoop<CAresEventHandler>,
        _timeout: Self::Timeout) {
        self.ares_channel.process_fd(c_ares::INVALID_FD, c_ares::INVALID_FD);
        event_loop.timeout_ms((), 500).unwrap();
    }
}

impl CAresEventHandler {
    fn new(ares_channel: c_ares::Channel) -> CAresEventHandler {
        CAresEventHandler {
            ares_channel: ares_channel,
            tracked_fds: HashSet::new(),
        }
    }
}

fn print_a_results(result: Result<c_ares::AResults, c_ares::AresError>) {
    println!("");
    match result {
        Err(e) => {
            let err_string = c_ares::str_error(e);
            println!("A lookup failed with error '{:}'", err_string);
        }
        Ok(a_results) => {
            println!("Successful A lookup...");
            println!("Hostname: {}", a_results.hostname());
            for addr in a_results.iter() {
                println!("{:}", addr);
            }
        }
    }
}

fn print_aaaa_results(result: Result<c_ares::AAAAResults, c_ares::AresError>) {
    println!("");
    match result {
        Err(e) => {
            let err_string = c_ares::str_error(e);
            println!("AAAA lookup failed with error '{:}'", err_string);
        }
        Ok(aaaa_results) => {
            println!("Successful AAAA lookup...");
            println!("Hostname: {}", aaaa_results.hostname());
            for addr in aaaa_results.iter() {
                println!("{:}", addr);
            }
        }
    }
}

fn print_cname_result(result: Result<c_ares::CNameResult, c_ares::AresError>) {
    println!("");
    match result {
        Err(e) => {
            let err_string = c_ares::str_error(e);
            println!("CNAME lookup failed with error '{:}'", err_string);
        }
        Ok(cname_result) => {
            println!("Successful CNAME lookup...");
            println!("{}", cname_result.cname());
        }
    }
}

fn print_mx_results(result: Result<c_ares::MXResults, c_ares::AresError>) {
    println!("");
    match result {
        Err(e) => {
            let err_string = c_ares::str_error(e);
            println!("MX lookup failed with error '{:}'", err_string);
        }
        Ok(mx_results) => {
            println!("Successful MX lookup...");
            for mx_result in mx_results.iter() {
                println!(
                    "host {}, priority {}",
                    mx_result.host(),
                    mx_result.priority());
            }
        }
    }
}

fn main() {
    // Create an event loop, and a c_ares::Channel.
    let mut event_loop = mio::EventLoop::new()
        .ok()
        .expect("failed to create event loop");
    let event_loop_channel = event_loop.channel();
    let event_loop_channel_clone = event_loop_channel.clone();
    let sock_callback = move |fd: io::RawFd, readable: bool, writable: bool| {
        event_loop_channel_clone
            .send(CAresHandlerMessage::RegisterInterest(fd, readable, writable))
            .ok()
            .expect("Failed to send RegisterInterest");
    };
    let mut options = c_ares::Options::new();
    options
        .set_flags(c_ares::flags::STAYOPEN | c_ares::flags::EDNS)
        .set_timeout(500)
        .set_tries(3);
    let mut ares_channel = c_ares::Channel::new(sock_callback, options)
        .ok()
        .expect("Failed to create channel");

    // Set up a couple of queries.
    let (results_tx, results_rx) = mpsc::channel();
    let tx = results_tx.clone();
    ares_channel.query_a("apple.com", move |results| {
        print_a_results(results);
        tx.send(()).unwrap()
    });

    let tx = results_tx.clone();
    ares_channel.query_aaaa("google.com", move |results| {
        print_aaaa_results(results);
        tx.send(()).unwrap()
    });

    let tx = results_tx.clone();
    ares_channel.query_cname("dimbleby.github.io", move |result| {
        print_cname_result(result);
        tx.send(()).unwrap()
    });

    let tx = results_tx.clone();
    ares_channel.query_mx("gmail.com", move |results| {
        print_mx_results(results);
        tx.send(()).unwrap()
    });

    // Set the first instance of the recurring timer on the event loop.
    event_loop.timeout_ms((), 500).unwrap();

    // Kick off the event loop.
    let mut event_handler = CAresEventHandler::new(ares_channel);
    let handle = thread::spawn(move || {
        event_loop
            .run(&mut event_handler)
            .ok()
            .expect("failed to run event loop")
    });

    // Wait for results to roll in.
    for _ in 0..4 {
        results_rx.recv().unwrap();
    }

    // Shut down the event loop and wait for it to finish.
    event_loop_channel
        .send(CAresHandlerMessage::ShutDown)
        .ok()
        .expect("failed to shut down event loop");
    handle.join().unwrap();
}
<|endoftext|>