<commit_before>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// 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; version
// 2.1 of the License.
//
// 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
//

use libimagstore::store::Store;
use libimagerror::trace::trace_error;
use std::error::Error;
use std::ops::Deref;
use std::ops::DerefMut;

use ruru::{Class, Object, AnyObject, Boolean, RString, VM, Hash, NilClass, VerifiedObject};

use ruby_utils::IntoToml;
use toml_utils::IntoRuby;
use util::Wrap;
use util::Unwrap;

use storeid::RStoreId;
use entry::RFileLockEntry;
use cache::StoreHandle;

wrappable_struct!(StoreHandle, StoreWrapper, STORE_WRAPPER);
class!(RStore);
impl_wrap!(StoreHandle, STORE_WRAPPER);
impl_unwrap!(RStore, StoreHandle, STORE_WRAPPER);
impl_verified_object!(RStore);

macro_rules! call_on_store_by_handle {
    {
        $store_handle:ident named $name:ident inside $operation:block
    }=> {{
        call_on_store_by_handle! {
            $store_handle
            named $name
            inside $operation
            on fail return NilClass::new().to_any_object()
        }
    }};

    {
        $store_handle:ident named $name:ident inside $operation:block on fail return $ex:expr
    } => {{
        use cache::RUBY_STORE_CACHE;

        let arc = RUBY_STORE_CACHE.clone();
        {
            let lock = arc.lock();
            match lock {
                Ok(mut hm) => {
                    match hm.get($store_handle) {
                        Some($name) => { $operation },
                        None => {
                            VM::raise(Class::from_existing("RuntimeError"),
                                    "Tried to operate on non-existing object");
                            $ex
                        }
                    }
                },
                Err(e) => {
                    VM::raise(Class::from_existing("RuntimeError"), e.description());
                    $ex
                }
            }
        }
    }};
}

macro_rules! call_on_store {
    {
        $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
        $fle_name:ident <- fetch $fle_handle_name:ident
        operation $operation:block
    } => {
        call_on_store! {
            $store_name <- $itself wrapped inside $wrapper,
            $fle_name <- fetch $fle_handle_name,
            operation $operation,
            on fail return NilClass::new()
        }
    };

    {
        $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
        $fle_name:ident <- fetch $fle_handle_name:ident,
        operation $operation:block,
        on fail return $fail_expr:expr
    } => {
        let handle = $itself.get_data(&*$wrapper);
        call_on_store_by_handle! {
            handle named $store_name inside {
                let $fle_name = match $store_name.get($fle_handle_name) {
                    Ok(Some(fle)) => fle,
                    Ok(None) => {
                        VM::raise(Class::from_existing("RuntimeError"), "Obj does not exist");
                        return $fail_expr
                    },
                    Err(e) => {
                        VM::raise(Class::from_existing("RuntimeError"), e.description());
                        return $fail_expr
                    },
                };
                $operation
            }
            on fail return $fail_expr
        }
    };

    {
        $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
        operation $operation:block,
        on fail return $fail_expr:expr
    } => {
        let handle = $itself.get_data(&*$wrapper);
        call_on_store_by_handle! {
            handle named $store_name inside $operation on fail return $fail_expr
        }
    };

    {
        $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
        operation $block
    } => {
        let handle = $itself.get_data(&*$wrapper);
        call_on_store_by_handle! { handle named $name inside $operation }
    };
}

methods!(
    RStore,
    itself,

    // Create an FileLockEntry in the store
    //
    // # Returns:
    //
    // On success: A RFileLockEntry
    // On failure: Nil
    // On error: Nil + Exception
    //
    fn create(id: RStoreId) -> AnyObject {
        use entry::FileLockEntryHandle;
        let sid = typecheck!(id or return any NilClass::new()).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            operation {
                match store.create(sid.clone()) {
                    Err(e) => {
                        trace_error(&e);
                        VM::raise(Class::from_existing("RuntimeError"), e.description());
                        NilClass::new().to_any_object()
                    },
                    Ok(entry) => {
                        // Take the location (StoreId) of the entry (we know it exists... so this
                        // is fine) and wrap it into a RFileLockEntry which is then returned to the
                        // user (as handle)
                        let sid = entry.get_location().clone();
                        let store_handle = itself.get_data(&*STORE_WRAPPER).clone();
                        FileLockEntryHandle::new(store_handle, sid).wrap()
                    },
                }
            },
            on fail return NilClass::new().to_any_object()
        }
    }

    // Retrieve an FileLockEntry from the store
    //
    // # Returns:
    //
    // On success: A RFileLockEntry
    // On error: Nil + Exception
    //
    fn retrieve(id: RStoreId) -> AnyObject {
        use entry::FileLockEntryHandle;
        let sid = typecheck!(id or return any NilClass::new()).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            operation {
                match store.retrieve(sid.clone()) {
                    Err(e) => {
                        trace_error(&e);
                        VM::raise(Class::from_existing("RuntimeError"), e.description());
                        NilClass::new().to_any_object()
                    },
                    Ok(entry) => {
                        // Take the location (StoreId) of the entry (we know it exists... so this
                        // is fine) and wrap it into a RFileLockEntry which is then returned to the
                        // user (as handle)
                        let sid = entry.get_location().clone();
                        let store_handle = itself.get_data(&*STORE_WRAPPER).clone();
                        FileLockEntryHandle::new(store_handle, sid).wrap()
                    },
                }
            },
            on fail return NilClass::new().to_any_object()
        }
    }

    // Get an FileLockEntry from the store
    //
    // # Returns:
    //
    // On success, if there is some: A RFileLockEntry
    // On success, if there is none: Nil
    // On error: Nil + Exception
    //
    fn get(sid: RStoreId) -> AnyObject {
        use entry::FileLockEntryHandle;
        let sid = typecheck!(sid or return any NilClass::new()).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            operation {
                match store.get(sid.clone()) {
                    Err(e) => {
                        trace_error(&e);
                        VM::raise(Class::from_existing("RuntimeError"), e.description());
                        NilClass::new().to_any_object()
                    },
                    Ok(None) => NilClass::new().to_any_object(),
                    Ok(Some(entry)) => {
                        // Take the location (StoreId) of the entry (we know it exists... so this
                        // is fine) and wrap it into a RFileLockEntry which is then returned to the
                        // user (as handle)
                        let sid = entry.get_location().clone();
                        let store_handle = itself.get_data(&*STORE_WRAPPER).clone();
                        FileLockEntryHandle::new(store_handle, sid).wrap()
                    },
                }
            },
            on fail return NilClass::new().to_any_object()
        }
    }

    // Get all FileLockEntry of a module from the store
    //
    // # Returns:
    //
    // On success: A Array[RFileLockEntry]
    // On error: Nil + Exception
    //
    fn retrieve_for_module(name: RString) -> AnyObject {
        unimplemented!()
    }

    // Update a FileLockEntry in the store
    //
    // # Returns:
    //
    // On success: Nil
    // On error: Nil + Exception
    //
    fn update(fle: RFileLockEntry) -> NilClass {
        let fle = typecheck!(fle).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            real_fle <- fetch fle,
            operation {
                if let Err(e) = store.update(real_fle) {
                    trace_error(&e);
                    VM::raise(Class::from_existing("RuntimeError"), e.description());
                }
                NilClass::new()
            },
            on fail return NilClass::new()
        }
    }

    // Delete a FileLockEntry from the store
    //
    // # Returns:
    //
    // On success: Nil
    // On error: Nil + Exception
    //
    fn delete(sid: RStoreId) -> NilClass {
        let sid = typecheck!(sid).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            operation {
                if let Err(e) = store.delete(sid) {
                    trace_error(&e);
                    VM::raise(Class::from_existing("RuntimeError"), e.description());
                }
                NilClass::new()
            },
            on fail return NilClass::new()
        }
    }

    // Save a FileLockEntry in a new path inside the store, keep the RFileLockEntry
    //
    // # Returns:
    //
    // On success: Nil
    // On error: Nil + Exception
    //
    fn save_to(fle: RFileLockEntry, sid: RStoreId) -> NilClass {
        let fle = typecheck!(fle).unwrap().clone();
        let sid = typecheck!(sid).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            real_fle <- fetch fle,
            operation {
                if let Err(e) = store.save_to(&real_fle, sid) {
                    trace_error(&e);
                    VM::raise(Class::from_existing("RuntimeError"), e.description());
                }
                NilClass::new()
            },
            on fail return NilClass::new()
        }
    }

    // Save a FileLockEntry in a new path inside the store, move the RFileLockEntry
    //
    // # Returns:
    //
    // On success: Nil
    // On error: Nil + Exception
    //
    fn save_as(fle: RFileLockEntry, sid: RStoreId) -> NilClass {
        let fle = typecheck!(fle).unwrap().clone();
        let sid = typecheck!(sid).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            real_fle <- fetch fle,
            operation {
                if let Err(e) = store.save_as(real_fle, sid) {
                    trace_error(&e);
                    VM::raise(Class::from_existing("RuntimeError"), e.description());
                }
                NilClass::new()
            },
            on fail return NilClass::new()
        }
    }

    // Move one entry in the store to another place, by its ID
    //
    // # Returns:
    //
    // On success: Nil
    // On error: Nil + Exception
    //
    fn move_by_id(old: RStoreId, nw: RStoreId) -> NilClass {
        let old = typecheck!(old).unwrap().clone();
        let nw  = typecheck!(nw).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            operation {
                if let Err(e) = store.move_by_id(old, nw) {
                    trace_error(&e);
                    VM::raise(Class::from_existing("RuntimeError"), e.description());
                }
                NilClass::new()
            },
            on fail return NilClass::new()
        }
    }

    // Get the path of the store object
    //
    // # Returns:
    //
    // A RString
    //
    fn path() -> RString {
        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            operation {
                store.path()
                    .clone()
                    .to_str()
                    .map(RString::new)
                    .unwrap_or(RString::new(""))
            },
            on fail return RString::new("")
        }
    }

);

pub fn setup() {
    unimplemented!()
}
<commit_msg>Add setup() for Store interface<commit_after>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// 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; version
// 2.1 of the License.
//
// 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
//

use libimagstore::store::Store;
use libimagerror::trace::trace_error;
use std::error::Error;
use std::ops::Deref;
use std::ops::DerefMut;

use ruru::{Class, Object, AnyObject, Boolean, RString, VM, Hash, NilClass, VerifiedObject};

use ruby_utils::IntoToml;
use toml_utils::IntoRuby;
use util::Wrap;
use util::Unwrap;

use storeid::RStoreId;
use entry::RFileLockEntry;
use cache::StoreHandle;

wrappable_struct!(StoreHandle, StoreWrapper, STORE_WRAPPER);
class!(RStore);
impl_wrap!(StoreHandle, STORE_WRAPPER);
impl_unwrap!(RStore, StoreHandle, STORE_WRAPPER);
impl_verified_object!(RStore);

macro_rules! call_on_store_by_handle {
    {
        $store_handle:ident named $name:ident inside $operation:block
    }=> {{
        call_on_store_by_handle! {
            $store_handle
            named $name
            inside $operation
            on fail return NilClass::new().to_any_object()
        }
    }};

    {
        $store_handle:ident named $name:ident inside $operation:block on fail return $ex:expr
    } => {{
        use cache::RUBY_STORE_CACHE;

        let arc = RUBY_STORE_CACHE.clone();
        {
            let lock = arc.lock();
            match lock {
                Ok(mut hm) => {
                    match hm.get($store_handle) {
                        Some($name) => { $operation },
                        None => {
                            VM::raise(Class::from_existing("RuntimeError"),
                                    "Tried to operate on non-existing object");
                            $ex
                        }
                    }
                },
                Err(e) => {
                    VM::raise(Class::from_existing("RuntimeError"), e.description());
                    $ex
                }
            }
        }
    }};
}

macro_rules! call_on_store {
    {
        $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
        $fle_name:ident <- fetch $fle_handle_name:ident
        operation $operation:block
    } => {
        call_on_store! {
            $store_name <- $itself wrapped inside $wrapper,
            $fle_name <- fetch $fle_handle_name,
            operation $operation,
            on fail return NilClass::new()
        }
    };

    {
        $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
        $fle_name:ident <- fetch $fle_handle_name:ident,
        operation $operation:block,
        on fail return $fail_expr:expr
    } => {
        let handle = $itself.get_data(&*$wrapper);
        call_on_store_by_handle! {
            handle named $store_name inside {
                let $fle_name = match $store_name.get($fle_handle_name) {
                    Ok(Some(fle)) => fle,
                    Ok(None) => {
                        VM::raise(Class::from_existing("RuntimeError"), "Obj does not exist");
                        return $fail_expr
                    },
                    Err(e) => {
                        VM::raise(Class::from_existing("RuntimeError"), e.description());
                        return $fail_expr
                    },
                };
                $operation
            }
            on fail return $fail_expr
        }
    };

    {
        $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
        operation $operation:block,
        on fail return $fail_expr:expr
    } => {
        let handle = $itself.get_data(&*$wrapper);
        call_on_store_by_handle! {
            handle named $store_name inside $operation on fail return $fail_expr
        }
    };

    {
        $store_name:ident <- $itself:ident wrapped inside $wrapper:ident,
        operation $block
    } => {
        let handle = $itself.get_data(&*$wrapper);
        call_on_store_by_handle! { handle named $name inside $operation }
    };
}

methods!(
    RStore,
    itself,

    // Create an FileLockEntry in the store
    //
    // # Returns:
    //
    // On success: A RFileLockEntry
    // On failure: Nil
    // On error: Nil + Exception
    //
    fn create(id: RStoreId) -> AnyObject {
        use entry::FileLockEntryHandle;
        let sid = typecheck!(id or return any NilClass::new()).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            operation {
                match store.create(sid.clone()) {
                    Err(e) => {
                        trace_error(&e);
                        VM::raise(Class::from_existing("RuntimeError"), e.description());
                        NilClass::new().to_any_object()
                    },
                    Ok(entry) => {
                        // Take the location (StoreId) of the entry (we know it exists... so this
                        // is fine) and wrap it into a RFileLockEntry which is then returned to the
                        // user (as handle)
                        let sid = entry.get_location().clone();
                        let store_handle = itself.get_data(&*STORE_WRAPPER).clone();
                        FileLockEntryHandle::new(store_handle, sid).wrap()
                    },
                }
            },
            on fail return NilClass::new().to_any_object()
        }
    }

    // Retrieve an FileLockEntry from the store
    //
    // # Returns:
    //
    // On success: A RFileLockEntry
    // On error: Nil + Exception
    //
    fn retrieve(id: RStoreId) -> AnyObject {
        use entry::FileLockEntryHandle;
        let sid = typecheck!(id or return any NilClass::new()).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            operation {
                match store.retrieve(sid.clone()) {
                    Err(e) => {
                        trace_error(&e);
                        VM::raise(Class::from_existing("RuntimeError"), e.description());
                        NilClass::new().to_any_object()
                    },
                    Ok(entry) => {
                        // Take the location (StoreId) of the entry (we know it exists... so this
                        // is fine) and wrap it into a RFileLockEntry which is then returned to the
                        // user (as handle)
                        let sid = entry.get_location().clone();
                        let store_handle = itself.get_data(&*STORE_WRAPPER).clone();
                        FileLockEntryHandle::new(store_handle, sid).wrap()
                    },
                }
            },
            on fail return NilClass::new().to_any_object()
        }
    }

    // Get an FileLockEntry from the store
    //
    // # Returns:
    //
    // On success, if there is some: A RFileLockEntry
    // On success, if there is none: Nil
    // On error: Nil + Exception
    //
    fn get(sid: RStoreId) -> AnyObject {
        use entry::FileLockEntryHandle;
        let sid = typecheck!(sid or return any NilClass::new()).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            operation {
                match store.get(sid.clone()) {
                    Err(e) => {
                        trace_error(&e);
                        VM::raise(Class::from_existing("RuntimeError"), e.description());
                        NilClass::new().to_any_object()
                    },
                    Ok(None) => NilClass::new().to_any_object(),
                    Ok(Some(entry)) => {
                        // Take the location (StoreId) of the entry (we know it exists... so this
                        // is fine) and wrap it into a RFileLockEntry which is then returned to the
                        // user (as handle)
                        let sid = entry.get_location().clone();
                        let store_handle = itself.get_data(&*STORE_WRAPPER).clone();
                        FileLockEntryHandle::new(store_handle, sid).wrap()
                    },
                }
            },
            on fail return NilClass::new().to_any_object()
        }
    }

    // Get all FileLockEntry of a module from the store
    //
    // # Returns:
    //
    // On success: A Array[RFileLockEntry]
    // On error: Nil + Exception
    //
    fn retrieve_for_module(name: RString) -> AnyObject {
        unimplemented!()
    }

    // Update a FileLockEntry in the store
    //
    // # Returns:
    //
    // On success: Nil
    // On error: Nil + Exception
    //
    fn update(fle: RFileLockEntry) -> NilClass {
        let fle = typecheck!(fle).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            real_fle <- fetch fle,
            operation {
                if let Err(e) = store.update(real_fle) {
                    trace_error(&e);
                    VM::raise(Class::from_existing("RuntimeError"), e.description());
                }
                NilClass::new()
            },
            on fail return NilClass::new()
        }
    }

    // Delete a FileLockEntry from the store
    //
    // # Returns:
    //
    // On success: Nil
    // On error: Nil + Exception
    //
    fn delete(sid: RStoreId) -> NilClass {
        let sid = typecheck!(sid).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            operation {
                if let Err(e) = store.delete(sid) {
                    trace_error(&e);
                    VM::raise(Class::from_existing("RuntimeError"), e.description());
                }
                NilClass::new()
            },
            on fail return NilClass::new()
        }
    }

    // Save a FileLockEntry in a new path inside the store, keep the RFileLockEntry
    //
    // # Returns:
    //
    // On success: Nil
    // On error: Nil + Exception
    //
    fn save_to(fle: RFileLockEntry, sid: RStoreId) -> NilClass {
        let fle = typecheck!(fle).unwrap().clone();
        let sid = typecheck!(sid).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            real_fle <- fetch fle,
            operation {
                if let Err(e) = store.save_to(&real_fle, sid) {
                    trace_error(&e);
                    VM::raise(Class::from_existing("RuntimeError"), e.description());
                }
                NilClass::new()
            },
            on fail return NilClass::new()
        }
    }

    // Save a FileLockEntry in a new path inside the store, move the RFileLockEntry
    //
    // # Returns:
    //
    // On success: Nil
    // On error: Nil + Exception
    //
    fn save_as(fle: RFileLockEntry, sid: RStoreId) -> NilClass {
        let fle = typecheck!(fle).unwrap().clone();
        let sid = typecheck!(sid).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            real_fle <- fetch fle,
            operation {
                if let Err(e) = store.save_as(real_fle, sid) {
                    trace_error(&e);
                    VM::raise(Class::from_existing("RuntimeError"), e.description());
                }
                NilClass::new()
            },
            on fail return NilClass::new()
        }
    }

    // Move one entry in the store to another place, by its ID
    //
    // # Returns:
    //
    // On success: Nil
    // On error: Nil + Exception
    //
    fn move_by_id(old: RStoreId, nw: RStoreId) -> NilClass {
        let old = typecheck!(old).unwrap().clone();
        let nw  = typecheck!(nw).unwrap().clone();

        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            operation {
                if let Err(e) = store.move_by_id(old, nw) {
                    trace_error(&e);
                    VM::raise(Class::from_existing("RuntimeError"), e.description());
                }
                NilClass::new()
            },
            on fail return NilClass::new()
        }
    }

    // Get the path of the store object
    //
    // # Returns:
    //
    // A RString
    //
    fn path() -> RString {
        call_on_store! {
            store <- itself wrapped inside STORE_WRAPPER,
            operation {
                store.path()
                    .clone()
                    .to_str()
                    .map(RString::new)
                    .unwrap_or(RString::new(""))
            },
            on fail return RString::new("")
        }
    }

);

pub fn setup() -> Class {
    let mut class = Class::new("RStore", None);
    class.define(|itself| {
        itself.def("create"              , create);
        itself.def("retrieve"            , retrieve);
        itself.def("get"                 , get);
        itself.def("retrieve_for_module" , retrieve_for_module);
        itself.def("update"              , update);
        itself.def("delete"              , delete);
        itself.def("save_to"             , save_to);
        itself.def("save_as"             , save_as);
        itself.def("move_by_id"          , move_by_id);
        itself.def("path"                , path);
    });
    class
}

<|endoftext|>


<commit_before>use super::super::{Y, X, Coord, Grid};
use grid_loop::{Edge, GridLoop, GridLoopField};
use super::*;

pub struct Field<'a> {
    grid_loop: GridLoop,
    clue: Grid<Clue>,
    dic: &'a Dictionary,
}
impl<'a> Field<'a> {
    pub fn new(clue: &Grid<Clue>, dic: &'a Dictionary) -> Field<'a> {
        let grid_loop = GridLoop::new(clue.height(), clue.width());

        Field {
            grid_loop: grid_loop,
            clue: clue.clone(),
            dic: dic,
        }
    }
    pub fn height(&self) -> i32 {
        self.clue.height()
    }
    pub fn width(&self) -> i32 {
        self.clue.width()
    }
    pub fn inconsistent(&self) -> bool {
        self.grid_loop.inconsistent()
    }
    pub fn set_inconsistent(&mut self) {
        self.grid_loop.set_inconsistent()
    }
    pub fn fully_solved(&self) -> bool {
        self.grid_loop.fully_solved()
    }
    pub fn check_all_cell(&mut self) {
        let height = self.height();
        let width = self.width();
        let mut handle = GridLoop::get_handle(self);
        for y in 0..height {
            for x in 0..width {
                let clue = handle.get_clue((Y(y), X(x)));
                if clue != NO_CLUE {
                    GridLoop::check(&mut *handle, (Y(y * 2 + 1), X(x * 2 + 1)));
                }
            }
        }
    }
    pub fn get_clue(&self, cd: Coord) -> Clue {
        self.clue[cd]
    }
    pub fn add_clue(&mut self, cd: Coord, clue: Clue) {
        if self.clue[cd] != NO_CLUE {
            if self.clue[cd] != clue {
                self.grid_loop.set_inconsistent();
            }
        } else {
            self.clue[cd] = clue;
            GridLoop::check(self, cd);
        }
    }
    pub fn get_edge(&mut self, cd: Coord) -> Edge {
        self.grid_loop.get_edge(cd)
    }
    pub fn get_edge_safe(&mut self, cd: Coord) -> Edge {
        self.grid_loop.get_edge_safe(cd)
    }
}
impl<'a> GridLoopField for Field<'a> {
    fn grid_loop(&mut self) -> &mut GridLoop {
        &mut self.grid_loop
    }
    fn check_neighborhood(&mut self, (Y(y), X(x)): Coord) {
        if y % 2 == 1 {
            GridLoop::check(self, (Y(y - 1), X(x)));
            GridLoop::check(self, (Y(y + 1), X(x)));

            GridLoop::check(self, (Y(y), X(x - 1)));
            GridLoop::check(self, (Y(y), X(x + 1)));
            GridLoop::check(self, (Y(y - 2), X(x - 1)));
            GridLoop::check(self, (Y(y - 2), X(x + 1)));
            GridLoop::check(self, (Y(y + 2), X(x - 1)));
            GridLoop::check(self, (Y(y + 2), X(x + 1)));
        } else {
            GridLoop::check(self, (Y(y), X(x - 1)));
            GridLoop::check(self, (Y(y), X(x + 1)));

            GridLoop::check(self, (Y(y - 1), X(x)));
            GridLoop::check(self, (Y(y + 1), X(x)));
            GridLoop::check(self, (Y(y - 1), X(x - 2)));
            GridLoop::check(self, (Y(y + 1), X(x - 2)));
            GridLoop::check(self, (Y(y - 1), X(x + 2)));
            GridLoop::check(self, (Y(y + 1), X(x + 2)));
        }
    }
    fn inspect(&mut self, (Y(y), X(x)): Coord) {
        if y % 2 == 1 && x % 2 == 1 {
            let clue = self.clue[(Y(y / 2), X(x / 2))];
            if clue == NO_CLUE { return; }

            let mut neighbors = [Edge::Undecided; DICTIONARY_NEIGHBOR_SIZE];
            for i in 0..DICTIONARY_NEIGHBOR_SIZE {
                let (Y(dy), X(dx)) = DICTIONARY_EDGE_OFFSET[i];
                neighbors[i] = self.grid_loop.get_edge_safe((Y(y + dy), X(x + dx)));
            }

            if self.dic.consult(clue, &mut neighbors) {
                self.grid_loop.set_inconsistent();
                return;
            }

            for i in 0..DICTIONARY_NEIGHBOR_SIZE {
                if neighbors[i] != Edge::Undecided {
                    let (Y(dy), X(dx)) = DICTIONARY_EDGE_OFFSET[i];
                    GridLoop::decide_edge(self, (Y(y + dy), X(x + dx)), neighbors[i]);
                }
            }
        }
    }
}

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

    fn run_problem_test(input: &[&str], dic: &Dictionary) {
        let height = (input.len() / 2) as i32;
        let width = (input[0].len() / 2) as i32;

        let mut clue = Grid::new(height, width, NO_CLUE);
        for y in 0..height {
            let mut row_iter = input[(y * 2 + 1) as usize].chars();

            for x in 0..width {
                row_iter.next();
                let c = row_iter.next().unwrap();
                if '0' <= c && c <= '3' {
                    clue[(Y(y), X(x))] = Clue(((c as u8) - ('0' as u8)) as i32);
                }
            }
        }

        let mut field = Field::new(&clue, dic);
        field.check_all_cell();

        for y in 0..(input.len() as i32) {
            let mut row_iter = input[y as usize].chars();

            for x in 0..(input[0].len() as i32) {
                let ch = row_iter.next().unwrap();

                if !field.grid_loop().is_edge((Y(y), X(x))) {
                    continue;
                }

                let expected_edge = match ch {
                    '|' | '-' => Edge::Line,
                    'x' => Edge::Blank,
                    _ => Edge::Undecided,
                };

                assert_eq!(field.get_edge((Y(y), X(x))), expected_edge, "Comparing at y={}, x={}", y, x);
            }
        }
    }

    #[test]
    fn test_problem() {
        let dic = Dictionary::complete();

        run_problem_test(&[
            "+x+-+-+ +",
            "x | x    ",
            "+x+-+x+ +",
            "x0x3|    ",
            "+x+-+x+ +",
            "x | x    ",
            "+x+-+-+ +",
        ], &dic);
        run_problem_test(&[
            "+x+-+x+x+",
            "x |3| x x",
            "+x+x+-+-+",
            "x | x x3|",
            "+x+-+x+-+",
            "x0x2| | x",
            "+x+x+-+x+",
        ], &dic);
        run_problem_test(&[
            "+-+-+-+-+",
            "|3x x x |",
            "+-+ +-+x+",
            "x     | |",
            "+x+ +x+-+",
            "x x x0x1x",
            "+x+x+x+x+",
        ], &dic);
        run_problem_test(&[
            "+ +-+-+x+",
            " 2  x2| x",
            "+ +x+x+-+",
            "| x x0x |",
            "+ + +x+ +",
            "         ",
            "+ + + + +",
        ], &dic);
    }
}
<commit_msg>Slitherlink: add techniques about adjacent/diagonal 3<commit_after>use super::super::{Y, X, Coord, Grid};
use grid_loop::{Edge, GridLoop, GridLoopField};
use super::*;

pub struct Field<'a> {
    grid_loop: GridLoop,
    clue: Grid<Clue>,
    dic: &'a Dictionary,
}
impl<'a> Field<'a> {
    pub fn new(clue: &Grid<Clue>, dic: &'a Dictionary) -> Field<'a> {
        let grid_loop = GridLoop::new(clue.height(), clue.width());

        Field {
            grid_loop: grid_loop,
            clue: clue.clone(),
            dic: dic,
        }
    }
    pub fn height(&self) -> i32 {
        self.clue.height()
    }
    pub fn width(&self) -> i32 {
        self.clue.width()
    }
    pub fn inconsistent(&self) -> bool {
        self.grid_loop.inconsistent()
    }
    pub fn set_inconsistent(&mut self) {
        self.grid_loop.set_inconsistent()
    }
    pub fn fully_solved(&self) -> bool {
        self.grid_loop.fully_solved()
    }
    pub fn check_all_cell(&mut self) {
        let height = self.height();
        let width = self.width();
        let mut handle = GridLoop::get_handle(self);
        for y in 0..height {
            for x in 0..width {
                let clue = handle.get_clue((Y(y), X(x)));
                if clue != NO_CLUE {
                    GridLoop::check(&mut *handle, (Y(y * 2 + 1), X(x * 2 + 1)));
                }
            }
        }
    }
    pub fn get_clue(&self, cd: Coord) -> Clue {
        self.clue[cd]
    }
    pub fn add_clue(&mut self, cd: Coord, clue: Clue) {
        if self.clue[cd] != NO_CLUE {
            if self.clue[cd] != clue {
                self.grid_loop.set_inconsistent();
            }
        } else {
            self.clue[cd] = clue;
            GridLoop::check(self, cd);
        }
    }
    pub fn get_edge(&mut self, cd: Coord) -> Edge {
        self.grid_loop.get_edge(cd)
    }
    pub fn get_edge_safe(&mut self, cd: Coord) -> Edge {
        self.grid_loop.get_edge_safe(cd)
    }

    fn inspect_technique(&mut self, (Y(y), X(x)): Coord) {
        if y % 2 == 1 && x % 2 == 1 {
            let clue = self.clue[(Y(y / 2), X(x / 2))];
            if clue == Clue(3) {
                let neighbor = [
                    (Y(1), X(0)),
                    (Y(0), X(1)),
                    (Y(-1), X(0)),
                    (Y(0), X(-1)),
                ];

                // adjacent 3
                for d in 0..4 {
                    let (Y(dy), X(dx)) = neighbor[d];
                    let cell2 = (Y(y / 2 + dy), X(x / 2 + dx));
                    if self.clue.is_valid_coord(cell2) && self.clue[cell2] == Clue(3) {
                        // Deriberately ignoring the possible small loop encircling the two 3's
                        GridLoop::decide_edge(self, (Y(y - dy), X(x - dx)), Edge::Line);
                        GridLoop::decide_edge(self, (Y(y + dy), X(x + dx)), Edge::Line);
                        GridLoop::decide_edge(self, (Y(y + 3 * dy), X(x + 3 * dx)), Edge::Line);
                        GridLoop::decide_edge(self, (Y(y + dy + 2 * dx), X(x + dx + 2 * dy)), Edge::Blank);
                        GridLoop::decide_edge(self, (Y(y + dy - 2 * dx), X(x + dx - 2 * dy)), Edge::Blank);
                    }
                }

                // diagonal 3
                for d in 0..4 {
                    let (Y(dy1), X(dx1)) = neighbor[d];
                    let (Y(dy2), X(dx2)) = neighbor[(d + 1) % 4];
                    let cell2 = (Y(y / 2 + dy1 + dy2), X(x / 2 + dx1 + dx2));
                    if self.clue.is_valid_coord(cell2) && self.clue[cell2] == Clue(3) {
                        GridLoop::decide_edge(self, (Y(y - dy1), X(x - dx1)), Edge::Line);
                        GridLoop::decide_edge(self, (Y(y - dy2), X(x - dx2)), Edge::Line);
                        GridLoop::decide_edge(self, (Y(y + 2 * dy1 + 3 * dy2), X(x + 2 * dx1 + 3 * dx2)), Edge::Line);
                        GridLoop::decide_edge(self, (Y(y + 3 * dy1 + 2 * dy2), X(x + 3 * dx1 + 2 * dx2)), Edge::Line);
                    }
                }
            }
        }
    }
}
impl<'a> GridLoopField for Field<'a> {
    fn grid_loop(&mut self) -> &mut GridLoop {
        &mut self.grid_loop
    }
    fn check_neighborhood(&mut self, (Y(y), X(x)): Coord) {
        if y % 2 == 1 {
            GridLoop::check(self, (Y(y - 1), X(x)));
            GridLoop::check(self, (Y(y + 1), X(x)));

            GridLoop::check(self, (Y(y), X(x - 1)));
            GridLoop::check(self, (Y(y), X(x + 1)));
            GridLoop::check(self, (Y(y - 2), X(x - 1)));
            GridLoop::check(self, (Y(y - 2), X(x + 1)));
            GridLoop::check(self, (Y(y + 2), X(x - 1)));
            GridLoop::check(self, (Y(y + 2), X(x + 1)));
        } else {
            GridLoop::check(self, (Y(y), X(x - 1)));
            GridLoop::check(self, (Y(y), X(x + 1)));

            GridLoop::check(self, (Y(y - 1), X(x)));
            GridLoop::check(self, (Y(y + 1), X(x)));
            GridLoop::check(self, (Y(y - 1), X(x - 2)));
            GridLoop::check(self, (Y(y + 1), X(x - 2)));
            GridLoop::check(self, (Y(y - 1), X(x + 2)));
            GridLoop::check(self, (Y(y + 1), X(x + 2)));
        }
    }
    fn inspect(&mut self, (Y(y), X(x)): Coord) {
        if y % 2 == 1 && x % 2 == 1 {
            let clue = self.clue[(Y(y / 2), X(x / 2))];
            if clue == NO_CLUE { return; }

            let mut neighbors = [Edge::Undecided; DICTIONARY_NEIGHBOR_SIZE];
            for i in 0..DICTIONARY_NEIGHBOR_SIZE {
                let (Y(dy), X(dx)) = DICTIONARY_EDGE_OFFSET[i];
                neighbors[i] = self.grid_loop.get_edge_safe((Y(y + dy), X(x + dx)));
            }

            if self.dic.consult(clue, &mut neighbors) {
                self.grid_loop.set_inconsistent();
                return;
            }

            for i in 0..DICTIONARY_NEIGHBOR_SIZE {
                if neighbors[i] != Edge::Undecided {
                    let (Y(dy), X(dx)) = DICTIONARY_EDGE_OFFSET[i];
                    GridLoop::decide_edge(self, (Y(y + dy), X(x + dx)), neighbors[i]);
                }
            }

            self.inspect_technique((Y(y), X(x)));
        }
    }
}

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

    fn run_problem_test(dic: &Dictionary, input: &[&str], fully_solved: bool) {
        let height = (input.len() / 2) as i32;
        let width = (input[0].len() / 2) as i32;

        let mut clue = Grid::new(height, width, NO_CLUE);
        for y in 0..height {
            let mut row_iter = input[(y * 2 + 1) as usize].chars();

            for x in 0..width {
                row_iter.next();
                let c = row_iter.next().unwrap();
                if '0' <= c && c <= '3' {
                    clue[(Y(y), X(x))] = Clue(((c as u8) - ('0' as u8)) as i32);
                }
            }
        }

        let mut field = Field::new(&clue, dic);
        field.check_all_cell();

        assert_eq!(field.inconsistent(), false);
        assert_eq!(field.fully_solved(), fully_solved);

        for y in 0..(input.len() as i32) {
            let mut row_iter = input[y as usize].chars();

            for x in 0..(input[0].len() as i32) {
                let ch = row_iter.next().unwrap();

                if !field.grid_loop().is_edge((Y(y), X(x))) {
                    continue;
                }

                let expected_edge = match ch {
                    '|' | '-' => Edge::Line,
                    'x' => Edge::Blank,
                    _ => Edge::Undecided,
                };

                assert_eq!(field.get_edge((Y(y), X(x))), expected_edge, "Comparing at y={}, x={}", y, x);
            }
        }
    }

    #[test]
    fn test_problem() {
        let dic = Dictionary::complete();

        run_problem_test(&dic, &[
            "+x+-+-+ +",
            "x | x    ",
            "+x+-+x+ +",
            "x0x3|    ",
            "+x+-+x+ +",
            "x | x    ",
            "+x+-+-+ +",
        ], false);
        run_problem_test(&dic, &[
            "+x+-+x+x+",
            "x |3| x x",
            "+x+x+-+-+",
            "x | x x3|",
            "+x+-+x+-+",
            "x0x2| | x",
            "+x+x+-+x+",
        ], true);
        run_problem_test(&dic, &[
            "+-+-+-+-+",
            "|3x x x |",
            "+-+ +-+x+",
            "x     | |",
            "+x+ +x+-+",
            "x x x0x1x",
            "+x+x+x+x+",
        ], false);
        run_problem_test(&dic, &[
            "+ +-+-+x+",
            " 2  x2| x",
            "+ +x+x+-+",
            "| x x0x |",
            "+ + +x+ +",
            "         ",
            "+ + + + +",
        ], false);
        run_problem_test(&dic, &[
            "+ +-+ +x+",
            "   3   1x",
            "+x+-+x+ +",
            "   3  |  ",
            "+ +-+ + +",
            "         ",
            "+ + + + +",
        ], false);
        run_problem_test(&dic, &[
            "+-+-+ + +",
            "|2x      ",
            "+x+-+ + +",
            "| |3     ",
            "+ + + + +",
            "     3| x",
            "+ + +-+x+",
        ], false);
    }
}
<|endoftext|>


<commit_before>// Copyright 2016 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

use storage::{Key, Value, KvPair, Snapshot, ScanMode, Statistics};
use storage::mvcc::{MvccReader, Error as MvccError};
use super::{Error, Result};

pub struct SnapshotStore<'a> {
    snapshot: &'a Snapshot,
    start_ts: u64,
}

impl<'a> SnapshotStore<'a> {
    pub fn new(snapshot: &'a Snapshot, start_ts: u64) -> SnapshotStore {
        SnapshotStore {
            snapshot: snapshot,
            start_ts: start_ts,
        }
    }

    pub fn get(&self, key: &Key, statistics: &mut Statistics) -> Result<Option<Value>> {
        let mut reader = MvccReader::new(self.snapshot, statistics, None, true, None);
        let v = try!(reader.get(key, self.start_ts));
        Ok(v)
    }

    pub fn batch_get(&self,
                     keys: &[Key],
                     statistics: &mut Statistics)
                     -> Result<Vec<Result<Option<Value>>>> {
        // TODO: sort the keys and use ScanMode::Forward
        let mut reader = MvccReader::new(self.snapshot, statistics, None, true, None);
        let mut results = Vec::with_capacity(keys.len());
        for k in keys {
            results.push(reader.get(k, self.start_ts).map_err(Error::from));
        }
        Ok(results)
    }

    /// Create a scanner.
    /// when key_only is true, all the returned value will be empty.
    pub fn scanner(&self,
                   mode: ScanMode,
                   key_only: bool,
                   upper_bound: Option<Vec<u8>>,
                   statistics: &'a mut Statistics)
                   -> Result<StoreScanner> {
        let mut reader = MvccReader::new(self.snapshot, statistics, Some(mode), true, upper_bound);
        reader.set_key_only(key_only);
        Ok(StoreScanner {
            reader: reader,
            start_ts: self.start_ts,
        })
    }
}

pub struct StoreScanner<'a> {
    reader: MvccReader<'a>,
    start_ts: u64,
}

impl<'a> StoreScanner<'a> {
    pub fn seek(&mut self, key: Key) -> Result<Option<(Key, Value)>> {
        Ok(try!(self.reader.seek(key, self.start_ts)))
    }

    pub fn reverse_seek(&mut self, key: Key) -> Result<Option<(Key, Value)>> {
        Ok(try!(self.reader.reverse_seek(key, self.start_ts)))
    }

    #[inline]
    fn handle_mvcc_err(e: MvccError, result: &mut Vec<Result<KvPair>>) -> Result<Key> {
        let key = if let MvccError::KeyIsLocked { key: ref k, .. } = e {
            Some(Key::from_raw(k))
        } else {
            None
        };
        match key {
            Some(k) => {
                result.push(Err(e.into()));
                Ok(k)
            }
            None => Err(e.into()),
        }
    }

    pub fn scan(&mut self, mut key: Key, limit: usize) -> Result<Vec<Result<KvPair>>> {
        let mut results = vec![];
        while results.len() < limit {
            match self.seek(key) {
                Ok(Some((k, v))) => {
                    results.push(Ok((try!(k.raw()), v)));
                    key = k;
                }
                Ok(None) => break,
                Err(Error::Mvcc(e)) => key = try!(StoreScanner::handle_mvcc_err(e, &mut results)),
                Err(e) => return Err(e),
            }
            key = key.append_ts(0);
        }
        Ok(results)
    }

    pub fn reverse_scan(&mut self, mut key: Key, limit: usize) -> Result<Vec<Result<KvPair>>> {
        let mut results = vec![];
        while results.len() < limit {
            match self.reverse_seek(key) {
                Ok(Some((k, v))) => {
                    results.push(Ok((try!(k.raw()), v)));
                    key = k;
                }
                Ok(None) => break,
                Err(Error::Mvcc(e)) => key = try!(StoreScanner::handle_mvcc_err(e, &mut results)),
                Err(e) => return Err(e),
            }
        }
        Ok(results)
    }
}
<commit_msg>storage/txn:add unit tests for store. (#1741)<commit_after>// Copyright 2016 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

use storage::{Key, Value, KvPair, Snapshot, ScanMode, Statistics};
use storage::mvcc::{MvccReader, Error as MvccError};
use super::{Error, Result};

pub struct SnapshotStore<'a> {
    snapshot: &'a Snapshot,
    start_ts: u64,
}

impl<'a> SnapshotStore<'a> {
    pub fn new(snapshot: &'a Snapshot, start_ts: u64) -> SnapshotStore {
        SnapshotStore {
            snapshot: snapshot,
            start_ts: start_ts,
        }
    }

    pub fn get(&self, key: &Key, statistics: &mut Statistics) -> Result<Option<Value>> {
        let mut reader = MvccReader::new(self.snapshot, statistics, None, true, None);
        let v = try!(reader.get(key, self.start_ts));
        Ok(v)
    }

    pub fn batch_get(&self,
                     keys: &[Key],
                     statistics: &mut Statistics)
                     -> Result<Vec<Result<Option<Value>>>> {
        // TODO: sort the keys and use ScanMode::Forward
        let mut reader = MvccReader::new(self.snapshot, statistics, None, true, None);
        let mut results = Vec::with_capacity(keys.len());
        for k in keys {
            results.push(reader.get(k, self.start_ts).map_err(Error::from));
        }
        Ok(results)
    }

    /// Create a scanner.
    /// when key_only is true, all the returned value will be empty.
    pub fn scanner(&self,
                   mode: ScanMode,
                   key_only: bool,
                   upper_bound: Option<Vec<u8>>,
                   statistics: &'a mut Statistics)
                   -> Result<StoreScanner> {
        let mut reader = MvccReader::new(self.snapshot, statistics, Some(mode), true, upper_bound);
        reader.set_key_only(key_only);
        Ok(StoreScanner {
            reader: reader,
            start_ts: self.start_ts,
        })
    }
}

pub struct StoreScanner<'a> {
    reader: MvccReader<'a>,
    start_ts: u64,
}

impl<'a> StoreScanner<'a> {
    pub fn seek(&mut self, key: Key) -> Result<Option<(Key, Value)>> {
        Ok(try!(self.reader.seek(key, self.start_ts)))
    }

    pub fn reverse_seek(&mut self, key: Key) -> Result<Option<(Key, Value)>> {
        Ok(try!(self.reader.reverse_seek(key, self.start_ts)))
    }

    #[inline]
    fn handle_mvcc_err(e: MvccError, result: &mut Vec<Result<KvPair>>) -> Result<Key> {
        let key = if let MvccError::KeyIsLocked { key: ref k, .. } = e {
            Some(Key::from_raw(k))
        } else {
            None
        };
        match key {
            Some(k) => {
                result.push(Err(e.into()));
                Ok(k)
            }
            None => Err(e.into()),
        }
    }

    pub fn scan(&mut self, mut key: Key, limit: usize) -> Result<Vec<Result<KvPair>>> {
        let mut results = vec![];
        while results.len() < limit {
            match self.seek(key) {
                Ok(Some((k, v))) => {
                    results.push(Ok((try!(k.raw()), v)));
                    key = k;
                }
                Ok(None) => break,
                Err(Error::Mvcc(e)) => key = try!(StoreScanner::handle_mvcc_err(e, &mut results)),
                Err(e) => return Err(e),
            }
            key = key.append_ts(0);
        }
        Ok(results)
    }

    pub fn reverse_scan(&mut self, mut key: Key, limit: usize) -> Result<Vec<Result<KvPair>>> {
        let mut results = vec![];
        while results.len() < limit {
            match self.reverse_seek(key) {
                Ok(Some((k, v))) => {
                    results.push(Ok((try!(k.raw()), v)));
                    key = k;
                }
                Ok(None) => break,
                Err(Error::Mvcc(e)) => key = try!(StoreScanner::handle_mvcc_err(e, &mut results)),
                Err(e) => return Err(e),
            }
        }
        Ok(results)
    }
}

#[cfg(test)]
mod test {
    use kvproto::kvrpcpb::Context;
    use super::SnapshotStore;
    use storage::mvcc::MvccTxn;
    use storage::{make_key, Mutation, ALL_CFS, Options, Statistics, ScanMode, KvPair, Value};
    use storage::engine::{self, Engine, TEMP_DIR, Snapshot};

    const KEY_PREFIX: &str = "key_prefix";
    const START_TS: u64 = 10;
    const COMMIT_TS: u64 = 20;
    const START_ID: u64 = 1000;

    struct TestStore {
        keys: Vec<String>,
        snapshot: Box<Snapshot>,
        ctx: Context,
        engine: Box<Engine>,
    }

    impl TestStore {
        fn new(key_num: u64) -> TestStore {
            let engine = engine::new_local_engine(TEMP_DIR, ALL_CFS).unwrap();
            let keys: Vec<String> =
                (START_ID..START_ID + key_num).map(|i| format!("{}{}", KEY_PREFIX, i)).collect();
            let ctx = Context::new();
            let snapshot = engine.snapshot(&ctx).unwrap();
            let mut store = TestStore {
                keys: keys,
                snapshot: snapshot,
                ctx: ctx,
                engine: engine,
            };
            store.init_data();
            store
        }

        #[inline]
        fn init_data(&mut self) {
            let primary_key = format!("{}{}", KEY_PREFIX, START_ID);
            let pk = primary_key.as_bytes();
            let mut statistics = Statistics::default();
            // do prewrite.
            {
                let mut txn = MvccTxn::new(self.snapshot.as_ref(), &mut statistics, START_TS, None);
                for key in &self.keys {
                    let key = key.as_bytes();
                    txn.prewrite(Mutation::Put((make_key(key), key.to_vec())),
                                  pk,
                                  &Options::default())
                        .unwrap();
                }
                self.engine.write(&self.ctx, txn.modifies()).unwrap();
            }
            self.refresh_snapshot();
            // do commit
            {
                let mut txn = MvccTxn::new(self.snapshot.as_ref(), &mut statistics, START_TS, None);
                for key in &self.keys {
                    let key = key.as_bytes();
                    txn.commit(&make_key(key), COMMIT_TS).unwrap();
                }
                self.engine.write(&self.ctx, txn.modifies()).unwrap();

            }
            self.refresh_snapshot();
        }

        #[inline]
        fn refresh_snapshot(&mut self) {
            self.snapshot = self.engine.snapshot(&self.ctx).unwrap()
        }

        fn store(&self) -> SnapshotStore {
            SnapshotStore::new(self.snapshot.as_ref(), COMMIT_TS + 1)
        }
    }

    #[test]
    fn test_snapshot_store_get() {
        let key_num = 100;
        let store = TestStore::new(key_num);
        let snapshot_store = store.store();
        let mut statistics = Statistics::default();
        for key in &store.keys {
            let key = key.as_bytes();
            let data = snapshot_store.get(&make_key(key), &mut statistics).unwrap();
            assert!(data.is_some(), "{:?} expect some, but got none", key);
        }
    }

    #[test]
    fn test_snapshot_store_batch_get() {
        let key_num = 100;
        let store = TestStore::new(key_num);
        let snapshot_store = store.store();
        let mut statistics = Statistics::default();
        let mut keys_list = Vec::new();
        for key in &store.keys {
            keys_list.push(make_key(key.as_bytes()));
        }
        let data = snapshot_store.batch_get(&keys_list, &mut statistics);
        assert!(data.is_ok(), "expect ok,while got {:?}", data.unwrap_err());
        for item in data.unwrap() {
            let item = item.unwrap();
            assert!(item.is_some(), "item expect some while get none");
        }
    }

    #[test]
    fn test_snapshot_store_scan() {
        let key_num = 100;
        let store = TestStore::new(key_num);
        let snapshot_store = store.store();
        let mut statistics = Statistics::default();
        let mut scanner = snapshot_store.scanner(ScanMode::Forward, false, None, &mut statistics)
            .unwrap();

        let key = format!("{}{}", KEY_PREFIX, START_ID);
        let start_key = make_key(key.as_bytes());
        let half = (key_num / 2) as usize;
        let expect = &store.keys[0..half];
        let result = scanner.scan(start_key, half).unwrap();
        let result: Vec<Option<KvPair>> = result.into_iter()
            .map(Result::ok)
            .collect();
        let expect: Vec<Option<KvPair>> = expect.into_iter()
            .map(|k| Some((k.clone().into_bytes(), k.clone().into_bytes())))
            .collect();
        assert_eq!(result, expect, "expect {:?}, but got {:?}", expect, result);
    }

    #[test]
    fn test_snapshot_store_reverse_scan() {
        let key_num = 100;
        let store = TestStore::new(key_num);
        let snapshot_store = store.store();
        let mut statistics = Statistics::default();
        let mut scanner = snapshot_store.scanner(ScanMode::Backward, false, None, &mut statistics)
            .unwrap();

        let half = (key_num / 2) as usize;
        let key = format!("{}{}", KEY_PREFIX, START_ID + (half as u64) - 1);
        let start_key = make_key(key.as_bytes());
        let expect = &store.keys[0..half - 1];
        let result = scanner.reverse_scan(start_key, half).unwrap();
        let result: Vec<Option<KvPair>> = result.into_iter()
            .map(Result::ok)
            .collect();

        let mut expect: Vec<Option<KvPair>> = expect.into_iter()
            .map(|k| Some((k.clone().into_bytes(), k.clone().into_bytes())))
            .collect();
        expect.reverse();

        assert_eq!(result, expect, "expect {:?}, but got {:?}", expect, result);
    }

    #[test]
    fn test_snapshot_store_seek() {
        let key_num = 100;
        let store = TestStore::new(key_num);
        let snapshot_store = store.store();
        let mut statistics = Statistics::default();
        let mut scanner = snapshot_store.scanner(ScanMode::Forward, false, None, &mut statistics)
            .unwrap();

        let key = format!("{}{}aaa", KEY_PREFIX, START_ID);
        let start_key = make_key(key.as_bytes());
        let result = scanner.seek(start_key).unwrap();
        let expect_key = format!("{}{}", KEY_PREFIX, START_ID + 1);
        let expect_value = expect_key.clone().into_bytes();
        let expect = Some((make_key(expect_key.as_bytes()), expect_value as Value));
        assert_eq!(result, expect, "expect {:?}, but got {:?}", expect, result);
    }

    #[test]
    fn test_snapshot_store_reverse_seek() {
        let key_num = 100;
        let store = TestStore::new(key_num);
        let snapshot_store = store.store();
        let mut statistics = Statistics::default();
        let mut scanner = snapshot_store.scanner(ScanMode::Backward, false, None, &mut statistics)
            .unwrap();

        let key = format!("{}{}aaa", KEY_PREFIX, START_ID);
        let start_key = make_key(key.as_bytes());
        let result = scanner.reverse_seek(start_key).unwrap();
        let expect_key = format!("{}{}", KEY_PREFIX, START_ID);
        let expect_value = expect_key.clone().into_bytes();
        let expect = Some((make_key(expect_key.as_bytes()), expect_value as Value));
        assert_eq!(result, expect, "expect {:?}, but got {:?}", expect, result);
    }
}
<|endoftext|>


<commit_before>use crate::{Crate, Error};
use std::{
    io,
    path::{Path, PathBuf},
};

pub struct BareIndex {
    path: PathBuf,
    pub url: String,
}

impl BareIndex {
    /// Creates a bare index from a provided URL, opening the same location on
    /// disk that cargo uses for that registry index.
    pub fn from_url(url: &str) -> Result<Self, Error> {
        let (dir_name, canonical_url) = url_to_local_dir(url)?;
        let mut path = home::cargo_home().unwrap_or_default();

        path.push("registry/index");
        path.push(dir_name);

        Ok(Self {
            path,
            url: canonical_url,
        })
    }

    /// Creates a bare index at the provided path with the specified repository URL.
    pub fn with_path(path: PathBuf, url: &str) -> Self {
        Self {
            path,
            url: url.to_owned(),
        }
    }

    /// Creates an index for the default crates.io registry, using the same
    /// disk location as cargo itself.
    pub fn new_cargo_default() -> Result<Self, Error> {
        Self::from_url(crate::INDEX_GIT_URL)
    }

    /// Opens the local index, which acts as a kind of lock for source control
    /// operations
    pub fn open_or_clone(&self) -> Result<BareIndexRepo<'_>, Error> {
        BareIndexRepo::new(self)
    }
}

pub struct BareIndexRepo<'a> {
    inner: &'a BareIndex,
    head: git2::Oid,
    repo: git2::Repository,
    /// This is safe as we implement Drop manually, dropping this tree
    /// reference before we drop the actual repo that its lifetime is actually
    /// tied to, which is basically the same way that cargo works
    tree: Option<git2::Tree<'static>>,
    head_str: String,
}

impl<'a> BareIndexRepo<'a> {
    fn new(index: &'a BareIndex) -> Result<Self, Error> {
        let exists = git2::Repository::discover(&index.path)
            .map(|repository| {
                repository
                    .find_remote("origin")
                    .ok()
                    // Cargo creates a checkout without an origin set,
                    // so default to true in case of missing origin
                    .map_or(true, |remote| {
                        remote.url().map_or(true, |url| url == index.url)
                    })
            })
            .unwrap_or(false);

        if !exists {
            git2::build::RepoBuilder::new()
                .fetch_options(crate::fetch_opts())
                .bare(true)
                .clone(&index.url, &index.path)?;
        }

        let repo = git2::Repository::open(&index.path)?;
        let head = repo
            // Fallback to HEAD, as a fresh clone won't have a FETCH_HEAD
            .refname_to_id("FETCH_HEAD")
            .or_else(|_| repo.refname_to_id("HEAD"))?;
        let head_str = head.to_string();

        let tree = {
            let commit = repo.find_commit(head)?;
            let tree = commit.tree()?;

            // See comment on self.tree
            unsafe { std::mem::transmute::<git2::Tree<'_>, git2::Tree<'static>>(tree) }
        };

        Ok(Self {
            inner: index,
            head,
            head_str,
            repo,
            tree: Some(tree),
        })
    }

    /// Fetches latest from the remote index repository. Note that using this
    /// method will mean no cache entries will be used, if a new commit is fetched
    /// from the repository, as their commit version will no longer match.
    pub fn retrieve(&mut self) -> Result<(), Error> {
        {
            let mut origin_remote = self
                .repo
                .find_remote("origin")
                .or_else(|_| self.repo.remote_anonymous(&self.inner.url))?;

            origin_remote.fetch(
                &["+refs/heads/*:refs/remotes/origin/*"],
                Some(&mut crate::fetch_opts()),
                None,
            )?;
        }

        let head = self
            .repo
            .refname_to_id("FETCH_HEAD")
            .or_else(|_| self.repo.refname_to_id("HEAD"))?;
        let head_str = head.to_string();

        let commit = self.repo.find_commit(head)?;
        let tree = commit.tree()?;

        // See comment on self.tree
        let tree = unsafe { std::mem::transmute::<git2::Tree<'_>, git2::Tree<'static>>(tree) };

        self.head = head;
        self.head_str = head_str;
        self.tree = Some(tree);

        Ok(())
    }

    /// Reads a crate from the index, it will attempt to use a cached entry if
    /// one is available, otherwise it will fallback to reading the crate
    /// directly from the git blob containing the crate information.
    pub fn crate_(&self, name: &str) -> Option<Crate> {
        let rel_path = match crate::crate_name_to_relative_path(name) {
            Some(rp) => rp,
            None => return None,
        };

        // Attempt to load the .cache/ entry first, this is purely an acceleration
        // mechanism and can fail for a few reasons that are non-fatal
        {
            let mut cache_path = self.inner.path.join(".cache");
            cache_path.push(&rel_path);
            if let Ok(cache_bytes) = std::fs::read(&cache_path) {
                if let Ok(krate) = Crate::from_cache_slice(&cache_bytes, &self.head_str) {
                    return Some(krate);
                }
            }
        }

        // Fallback to reading the blob directly via git if we don't have a
        // valid cache entry
        self.krate_from_blob(&rel_path).ok()
    }

    fn krate_from_blob(&self, path: &str) -> Result<Crate, Error> {
        let entry = self.tree.as_ref().unwrap().get_path(&Path::new(path))?;
        let object = entry.to_object(&self.repo)?;
        let blob = object
            .as_blob()
            .ok_or_else(|| Error::Io(io::Error::new(io::ErrorKind::NotFound, path.to_owned())))?;

        Crate::from_slice(blob.content()).map_err(Error::Io)
    }
}

impl<'a> Drop for BareIndexRepo<'a> {
    fn drop(&mut self) {
        // Just be sure to drop this before our other fields
        self.tree.take();
    }
}

/// Converts a full url, eg https://github.com/rust-lang/crates.io-index, into
/// the root directory name where cargo itself will fetch it on disk
fn url_to_local_dir(url: &str) -> Result<(String, String), Error> {
    fn to_hex(num: u64) -> String {
        const CHARS: &[u8] = b"0123456789abcdef";

        let bytes = &[
            num as u8,
            (num >> 8) as u8,
            (num >> 16) as u8,
            (num >> 24) as u8,
            (num >> 32) as u8,
            (num >> 40) as u8,
            (num >> 48) as u8,
            (num >> 56) as u8,
        ];

        let mut output = vec![0u8; 16];

        let mut ind = 0;

        for &byte in bytes {
            output[ind] = CHARS[(byte >> 4) as usize];
            output[ind + 1] = CHARS[(byte & 0xf) as usize];

            ind += 2;
        }

        String::from_utf8(output).expect("valid utf-8 hex string")
    }

    #[allow(deprecated)]
    fn hash_u64(url: &str) -> u64 {
        use std::hash::{Hash, Hasher, SipHasher};

        let mut hasher = SipHasher::new_with_keys(0, 0);
        // Registry
        2usize.hash(&mut hasher);
        // Url
        url.hash(&mut hasher);
        hasher.finish()
    }

    // Ensure we have a registry or bare url
    let (url, scheme_ind) = {
        let scheme_ind = url
            .find("://")
            .ok_or_else(|| Error::Url(format!("'{}' is not a valid url", url)))?;

        let scheme_str = &url[..scheme_ind];
        if let Some(ind) = scheme_str.find('+') {
            if &scheme_str[..ind] != "registry" {
                return Err(Error::Url(format!("'{}' is not a valid registry url", url)));
            }

            (&url[ind + 1..], scheme_ind - ind - 1)
        } else {
            (url, scheme_ind)
        }
    };

    // Could use the Url crate for this, but it's simple enough and we don't
    // need to deal with every possible url (I hope...)
    let host = match url[scheme_ind + 3..].find('/') {
        Some(end) => &url[scheme_ind + 3..scheme_ind + 3 + end],
        None => &url[scheme_ind + 3..],
    };

    // cargo special cases github.com for reasons, so do the same
    let mut canonical = if host == "github.com" {
        url.to_lowercase()
    } else {
        url.to_owned()
    };

    // Chop off any query params/fragments
    if let Some(hash) = canonical.rfind('#') {
        canonical.truncate(hash);
    }

    if let Some(query) = canonical.rfind('?') {
        canonical.truncate(query);
    }

    let ident = to_hex(hash_u64(&canonical));

    if canonical.ends_with('/') {
        canonical.pop();
    }

    if canonical.ends_with(".git") {
        canonical.truncate(canonical.len() - 4);
    }

    Ok((format!("{}-{}", host, ident), canonical))
}

#[cfg(test)]
mod test {
    #[test]
    fn matches_cargo() {
        assert_eq!(
            super::url_to_local_dir(crate::INDEX_GIT_URL).unwrap(),
            (
                "github.com-1ecc6299db9ec823".to_owned(),
                crate::INDEX_GIT_URL.to_owned()
            )
        );

        // I've confirmed this also works with a custom registry, unfortunately
        // that one includes a secret key as part of the url which would allow
        // anyone to publish to the registry, so uhh...here's a fake one instead
        assert_eq!(
            super::url_to_local_dir(
                "https://dl.cloudsmith.io/aBcW1234aBcW1234/embark/rust/cargo/index.git"
            )
            .unwrap(),
            (
                "dl.cloudsmith.io-ff79e51ddd2b38fd".to_owned(),
                "https://dl.cloudsmith.io/aBcW1234aBcW1234/embark/rust/cargo/index".to_owned()
            )
        );

        // Ensure we actually strip off the irrelevant parts of a url, note that
        // the .git suffix is not part of the canonical url, but *is* used when hashing
        assert_eq!(
            super::url_to_local_dir(&format!(
                "registry+{}.git?one=1&two=2#fragment",
                crate::INDEX_GIT_URL
            ))
            .unwrap(),
            (
                "github.com-c786010fb7ef2e6e".to_owned(),
                crate::INDEX_GIT_URL.to_owned()
            )
        );
    }

    #[test]
    fn clones_bare_index() {
        use super::BareIndex;

        let tmp_dir = tempdir::TempDir::new("clones_bare_index").unwrap();

        let index = BareIndex::with_path(tmp_dir.path().to_owned(), crate::INDEX_GIT_URL);

        let mut repo = index
            .open_or_clone()
            .expect("Failed to clone crates.io index");

        fn test_sval(repo: &super::BareIndexRepo<'_>) {
            let krate = repo
                .crate_("sval")
                .expect("Could not find the crate sval in the index");

            let version = krate
                .versions()
                .iter()
                .find(|v| v.version() == "0.0.1")
                .expect("Version 0.0.1 of sval does not exist?");
            let dep_with_package_name = version
                .dependencies()
                .iter()
                .find(|d| d.name() == "serde_lib")
                .expect("sval does not have expected dependency?");
            assert_ne!(
                dep_with_package_name.name(),
                dep_with_package_name.package().unwrap()
            );
            assert_eq!(
                dep_with_package_name.crate_name(),
                dep_with_package_name.package().unwrap()
            );
        }

        test_sval(&repo);

        repo.retrieve().expect("Failed to fetch crates.io index");

        test_sval(&repo);
    }

    #[test]
    fn opens_bare_index() {
        use super::BareIndex;

        let tmp_dir = tempdir::TempDir::new("opens_bare_index").unwrap();

        let index = BareIndex::with_path(tmp_dir.path().to_owned(), crate::INDEX_GIT_URL);

        {
            let _ = index
                .open_or_clone()
                .expect("Failed to clone crates.io index");
        }

        let mut repo = index
            .open_or_clone()
            .expect("Failed to open crates.io index");

        fn test_sval(repo: &super::BareIndexRepo<'_>) {
            let krate = repo
                .crate_("sval")
                .expect("Could not find the crate sval in the index");

            let version = krate
                .versions()
                .iter()
                .find(|v| v.version() == "0.0.1")
                .expect("Version 0.0.1 of sval does not exist?");
            let dep_with_package_name = version
                .dependencies()
                .iter()
                .find(|d| d.name() == "serde_lib")
                .expect("sval does not have expected dependency?");
            assert_ne!(
                dep_with_package_name.name(),
                dep_with_package_name.package().unwrap()
            );
            assert_eq!(
                dep_with_package_name.crate_name(),
                dep_with_package_name.package().unwrap()
            );
        }

        test_sval(&repo);

        repo.retrieve().expect("Failed to fetch crates.io index");

        test_sval(&repo);
    }
}
<commit_msg>Inline unwrap<commit_after>use crate::{Crate, Error};
use std::{
    io,
    path::{Path, PathBuf},
};

pub struct BareIndex {
    path: PathBuf,
    pub url: String,
}

impl BareIndex {
    /// Creates a bare index from a provided URL, opening the same location on
    /// disk that cargo uses for that registry index.
    pub fn from_url(url: &str) -> Result<Self, Error> {
        let (dir_name, canonical_url) = url_to_local_dir(url)?;
        let mut path = home::cargo_home().unwrap_or_default();

        path.push("registry/index");
        path.push(dir_name);

        Ok(Self {
            path,
            url: canonical_url,
        })
    }

    /// Creates a bare index at the provided path with the specified repository URL.
    pub fn with_path(path: PathBuf, url: &str) -> Self {
        Self {
            path,
            url: url.to_owned(),
        }
    }

    /// Creates an index for the default crates.io registry, using the same
    /// disk location as cargo itself.
    pub fn new_cargo_default() -> Self {
        // UNWRAP: The default index git URL is known to safely convert to a path.
        Self::from_url(crate::INDEX_GIT_URL).unwrap()
    }

    /// Opens the local index, which acts as a kind of lock for source control
    /// operations
    pub fn open_or_clone(&self) -> Result<BareIndexRepo<'_>, Error> {
        BareIndexRepo::new(self)
    }
}

pub struct BareIndexRepo<'a> {
    inner: &'a BareIndex,
    head: git2::Oid,
    repo: git2::Repository,
    /// This is safe as we implement Drop manually, dropping this tree
    /// reference before we drop the actual repo that its lifetime is actually
    /// tied to, which is basically the same way that cargo works
    tree: Option<git2::Tree<'static>>,
    head_str: String,
}

impl<'a> BareIndexRepo<'a> {
    fn new(index: &'a BareIndex) -> Result<Self, Error> {
        let exists = git2::Repository::discover(&index.path)
            .map(|repository| {
                repository
                    .find_remote("origin")
                    .ok()
                    // Cargo creates a checkout without an origin set,
                    // so default to true in case of missing origin
                    .map_or(true, |remote| {
                        remote.url().map_or(true, |url| url == index.url)
                    })
            })
            .unwrap_or(false);

        if !exists {
            git2::build::RepoBuilder::new()
                .fetch_options(crate::fetch_opts())
                .bare(true)
                .clone(&index.url, &index.path)?;
        }

        let repo = git2::Repository::open(&index.path)?;
        let head = repo
            // Fallback to HEAD, as a fresh clone won't have a FETCH_HEAD
            .refname_to_id("FETCH_HEAD")
            .or_else(|_| repo.refname_to_id("HEAD"))?;
        let head_str = head.to_string();

        let tree = {
            let commit = repo.find_commit(head)?;
            let tree = commit.tree()?;

            // See comment on self.tree
            unsafe { std::mem::transmute::<git2::Tree<'_>, git2::Tree<'static>>(tree) }
        };

        Ok(Self {
            inner: index,
            head,
            head_str,
            repo,
            tree: Some(tree),
        })
    }

    /// Fetches latest from the remote index repository. Note that using this
    /// method will mean no cache entries will be used, if a new commit is fetched
    /// from the repository, as their commit version will no longer match.
    pub fn retrieve(&mut self) -> Result<(), Error> {
        {
            let mut origin_remote = self
                .repo
                .find_remote("origin")
                .or_else(|_| self.repo.remote_anonymous(&self.inner.url))?;

            origin_remote.fetch(
                &["+refs/heads/*:refs/remotes/origin/*"],
                Some(&mut crate::fetch_opts()),
                None,
            )?;
        }

        let head = self
            .repo
            .refname_to_id("FETCH_HEAD")
            .or_else(|_| self.repo.refname_to_id("HEAD"))?;
        let head_str = head.to_string();

        let commit = self.repo.find_commit(head)?;
        let tree = commit.tree()?;

        // See comment on self.tree
        let tree = unsafe { std::mem::transmute::<git2::Tree<'_>, git2::Tree<'static>>(tree) };

        self.head = head;
        self.head_str = head_str;
        self.tree = Some(tree);

        Ok(())
    }

    /// Reads a crate from the index, it will attempt to use a cached entry if
    /// one is available, otherwise it will fallback to reading the crate
    /// directly from the git blob containing the crate information.
    pub fn crate_(&self, name: &str) -> Option<Crate> {
        let rel_path = match crate::crate_name_to_relative_path(name) {
            Some(rp) => rp,
            None => return None,
        };

        // Attempt to load the .cache/ entry first, this is purely an acceleration
        // mechanism and can fail for a few reasons that are non-fatal
        {
            let mut cache_path = self.inner.path.join(".cache");
            cache_path.push(&rel_path);
            if let Ok(cache_bytes) = std::fs::read(&cache_path) {
                if let Ok(krate) = Crate::from_cache_slice(&cache_bytes, &self.head_str) {
                    return Some(krate);
                }
            }
        }

        // Fallback to reading the blob directly via git if we don't have a
        // valid cache entry
        self.krate_from_blob(&rel_path).ok()
    }

    fn krate_from_blob(&self, path: &str) -> Result<Crate, Error> {
        let entry = self.tree.as_ref().unwrap().get_path(&Path::new(path))?;
        let object = entry.to_object(&self.repo)?;
        let blob = object
            .as_blob()
            .ok_or_else(|| Error::Io(io::Error::new(io::ErrorKind::NotFound, path.to_owned())))?;

        Crate::from_slice(blob.content()).map_err(Error::Io)
    }
}

impl<'a> Drop for BareIndexRepo<'a> {
    fn drop(&mut self) {
        // Just be sure to drop this before our other fields
        self.tree.take();
    }
}

/// Converts a full url, eg https://github.com/rust-lang/crates.io-index, into
/// the root directory name where cargo itself will fetch it on disk
fn url_to_local_dir(url: &str) -> Result<(String, String), Error> {
    fn to_hex(num: u64) -> String {
        const CHARS: &[u8] = b"0123456789abcdef";

        let bytes = &[
            num as u8,
            (num >> 8) as u8,
            (num >> 16) as u8,
            (num >> 24) as u8,
            (num >> 32) as u8,
            (num >> 40) as u8,
            (num >> 48) as u8,
            (num >> 56) as u8,
        ];

        let mut output = vec![0u8; 16];

        let mut ind = 0;

        for &byte in bytes {
            output[ind] = CHARS[(byte >> 4) as usize];
            output[ind + 1] = CHARS[(byte & 0xf) as usize];

            ind += 2;
        }

        String::from_utf8(output).expect("valid utf-8 hex string")
    }

    #[allow(deprecated)]
    fn hash_u64(url: &str) -> u64 {
        use std::hash::{Hash, Hasher, SipHasher};

        let mut hasher = SipHasher::new_with_keys(0, 0);
        // Registry
        2usize.hash(&mut hasher);
        // Url
        url.hash(&mut hasher);
        hasher.finish()
    }

    // Ensure we have a registry or bare url
    let (url, scheme_ind) = {
        let scheme_ind = url
            .find("://")
            .ok_or_else(|| Error::Url(format!("'{}' is not a valid url", url)))?;

        let scheme_str = &url[..scheme_ind];
        if let Some(ind) = scheme_str.find('+') {
            if &scheme_str[..ind] != "registry" {
                return Err(Error::Url(format!("'{}' is not a valid registry url", url)));
            }

            (&url[ind + 1..], scheme_ind - ind - 1)
        } else {
            (url, scheme_ind)
        }
    };

    // Could use the Url crate for this, but it's simple enough and we don't
    // need to deal with every possible url (I hope...)
    let host = match url[scheme_ind + 3..].find('/') {
        Some(end) => &url[scheme_ind + 3..scheme_ind + 3 + end],
        None => &url[scheme_ind + 3..],
    };

    // cargo special cases github.com for reasons, so do the same
    let mut canonical = if host == "github.com" {
        url.to_lowercase()
    } else {
        url.to_owned()
    };

    // Chop off any query params/fragments
    if let Some(hash) = canonical.rfind('#') {
        canonical.truncate(hash);
    }

    if let Some(query) = canonical.rfind('?') {
        canonical.truncate(query);
    }

    let ident = to_hex(hash_u64(&canonical));

    if canonical.ends_with('/') {
        canonical.pop();
    }

    if canonical.ends_with(".git") {
        canonical.truncate(canonical.len() - 4);
    }

    Ok((format!("{}-{}", host, ident), canonical))
}

#[cfg(test)]
mod test {
    #[test]
    fn matches_cargo() {
        assert_eq!(
            super::url_to_local_dir(crate::INDEX_GIT_URL).unwrap(),
            (
                "github.com-1ecc6299db9ec823".to_owned(),
                crate::INDEX_GIT_URL.to_owned()
            )
        );

        // I've confirmed this also works with a custom registry, unfortunately
        // that one includes a secret key as part of the url which would allow
        // anyone to publish to the registry, so uhh...here's a fake one instead
        assert_eq!(
            super::url_to_local_dir(
                "https://dl.cloudsmith.io/aBcW1234aBcW1234/embark/rust/cargo/index.git"
            )
            .unwrap(),
            (
                "dl.cloudsmith.io-ff79e51ddd2b38fd".to_owned(),
                "https://dl.cloudsmith.io/aBcW1234aBcW1234/embark/rust/cargo/index".to_owned()
            )
        );

        // Ensure we actually strip off the irrelevant parts of a url, note that
        // the .git suffix is not part of the canonical url, but *is* used when hashing
        assert_eq!(
            super::url_to_local_dir(&format!(
                "registry+{}.git?one=1&two=2#fragment",
                crate::INDEX_GIT_URL
            ))
            .unwrap(),
            (
                "github.com-c786010fb7ef2e6e".to_owned(),
                crate::INDEX_GIT_URL.to_owned()
            )
        );
    }

    #[test]
    fn clones_bare_index() {
        use super::BareIndex;

        let tmp_dir = tempdir::TempDir::new("clones_bare_index").unwrap();

        let index = BareIndex::with_path(tmp_dir.path().to_owned(), crate::INDEX_GIT_URL);

        let mut repo = index
            .open_or_clone()
            .expect("Failed to clone crates.io index");

        fn test_sval(repo: &super::BareIndexRepo<'_>) {
            let krate = repo
                .crate_("sval")
                .expect("Could not find the crate sval in the index");

            let version = krate
                .versions()
                .iter()
                .find(|v| v.version() == "0.0.1")
                .expect("Version 0.0.1 of sval does not exist?");
            let dep_with_package_name = version
                .dependencies()
                .iter()
                .find(|d| d.name() == "serde_lib")
                .expect("sval does not have expected dependency?");
            assert_ne!(
                dep_with_package_name.name(),
                dep_with_package_name.package().unwrap()
            );
            assert_eq!(
                dep_with_package_name.crate_name(),
                dep_with_package_name.package().unwrap()
            );
        }

        test_sval(&repo);

        repo.retrieve().expect("Failed to fetch crates.io index");

        test_sval(&repo);
    }

    #[test]
    fn opens_bare_index() {
        use super::BareIndex;

        let tmp_dir = tempdir::TempDir::new("opens_bare_index").unwrap();

        let index = BareIndex::with_path(tmp_dir.path().to_owned(), crate::INDEX_GIT_URL);

        {
            let _ = index
                .open_or_clone()
                .expect("Failed to clone crates.io index");
        }

        let mut repo = index
            .open_or_clone()
            .expect("Failed to open crates.io index");

        fn test_sval(repo: &super::BareIndexRepo<'_>) {
            let krate = repo
                .crate_("sval")
                .expect("Could not find the crate sval in the index");

            let version = krate
                .versions()
                .iter()
                .find(|v| v.version() == "0.0.1")
                .expect("Version 0.0.1 of sval does not exist?");
            let dep_with_package_name = version
                .dependencies()
                .iter()
                .find(|d| d.name() == "serde_lib")
                .expect("sval does not have expected dependency?");
            assert_ne!(
                dep_with_package_name.name(),
                dep_with_package_name.package().unwrap()
            );
            assert_eq!(
                dep_with_package_name.crate_name(),
                dep_with_package_name.package().unwrap()
            );
        }

        test_sval(&repo);

        repo.retrieve().expect("Failed to fetch crates.io index");

        test_sval(&repo);
    }
}
<|endoftext|>