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

//! Volume Groups

use std::io::Result;
use std::io::Error;
use std::io::ErrorKind::Other;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::str::FromStr;
use std::borrow::Cow;

use time::now;
use nix::sys::utsname::uname;

use lv::{LV, Segment};
use pv::{PV, Device};
use pvlabel::{PvHeader, SECTOR_SIZE};
use parser::{LvmTextMap, Entry};
use lvmetad;
use dm;
use dm::DM;
use util::{align_to, make_uuid};

/// A Volume Group.
#[derive(Debug, PartialEq, Clone)]
pub struct VG {
    /// Name.
    pub name: String,
    /// Uuid.
    pub id: String,
    /// The generation of metadata this VG represents.
    pub seqno: u64,
    /// Always "LVM2".
    pub format: String,
    /// Status.
    pub status: Vec<String>,
    /// Flags.
    pub flags: Vec<String>,
    /// Size of each extent, in 512-byte sectors.
    pub extent_size: u64,
    /// Maximum number of LVs, 0 means no limit.
    pub max_lv: u64,
    /// Maximum number of PVs, 0 means no limit.
    pub max_pv: u64,
    /// How many metadata copies (?)
    pub metadata_copies: u64,
    /// Physical Volumes within this volume group.
    pub pvs: BTreeMap<String, PV>,
    /// Logical Volumes within this volume group.
    pub lvs: BTreeMap<String, LV>,
}

impl VG {
    /// The total number of extents in use in the volume group.
    pub fn extents_in_use(&self) -> u64 {
        self.lvs
            .values()
            .map(|x| x.used_extents())
            .sum()
    }

    /// The total number of free extents in the volume group.
    pub fn extents_free(&self) -> u64 {
        self.extents() - self.extents_in_use()
    }

    /// The total number of extents in the volume group.
    pub fn extents(&self) -> u64 {
        self.pvs
            .values()
            .map(|x| x.pe_count)
            .sum()
    }

    // Recursively walk DM deps to see if device is present
    fn depends_on(dev: Device, dm_majors: &BTreeSet<u32>, dm: &DM) -> bool {
        if !dm_majors.contains(&dev.major) {
            return false;
        }

        if let Ok(dep_list) = dm.list_deps(dev) {
            for d in dep_list {
                if d == dev {
                    return true;
                } else if Self::depends_on(d, dm_majors, dm) {
                    return true;
                }
            }
        }

        false
    }

    /// Add a non-affiliated PV to this VG.
    pub fn add_pv(&mut self, pvh: &PvHeader) -> Result<()> {
        // Check pv is not on an LV from the vg:
        // 1) is pv's major a devicemapper major?
        // 2) Walk dm deps (equiv. of LVM2 dev_manager_device_uses_vg)
        let dm_majors = dm::dev_majors();
        let dev = try!(Device::from_str(&pvh.dev_path.to_string_lossy()));
        {
            let dm = try!(DM::new(&self));
            if Self::depends_on(dev, &dm_majors, &dm) {
                return Err(Error::new(Other, "Dependency loops prohibited"));
            }
        }

        // check pv is not already in the VG or another VG
        // Does it have text metadata??
        if let Ok(metadata) = pvh.read_metadata() {
            // Find the textmap for the vg, among all the other stuff.
            // (It's the only textmap.)
            let mut vg_name = Cow::Borrowed("<unknown>");
            for (key, value) in metadata {
                match value {
                    Entry::TextMap(_) => {
                        vg_name = Cow::Owned(key);
                        break
                    },
                    _ => {}
                }
            }

            return Err(Error::new(Other, format!("PV already in VG {}", vg_name)));
        }

        let da = try!(pvh.data_areas.get(0)
                      .ok_or(Error::new(Other, "Could not find data area in PV")));

        // figure out how many extents fit in the PV's data area
        // pe_start aligned to extent size
        let dev_size_sectors = pvh.size / SECTOR_SIZE as u64;
        let pe_start_sectors = align_to(
            (da.offset / SECTOR_SIZE as u64) as usize,
            self.extent_size as usize) as u64;
        let mda1_size_sectors = match pvh.metadata_areas.get(1) {
            Some(pvarea) => pvarea.size / SECTOR_SIZE as u64,
            None => 0,
        };
        let area_size_sectors = dev_size_sectors - pe_start_sectors - mda1_size_sectors;
        let pe_count = area_size_sectors / self.extent_size;

        let name = format!("pv{}", self.pvs.len());

        // make a PV and add it to self
        let pv = PV {
            name: name.clone(),
            id: pvh.uuid.clone(),
            device: dev,
            status: vec!["ALLOCATABLE".to_string()],
            flags: Vec::new(),
            dev_size: dev_size_sectors,
            pe_start: pe_start_sectors,
            pe_count: pe_count,
        };

        self.pvs.insert(name, pv);

        self.commit()
    }

    /// Create a new linear logical volume in the volume group.
    pub fn new_linear_lv(&mut self, name: &str, extent_size: u64) -> Result<()> {
        if self.lvs.contains_key(name) {
            return Err(Error::new(Other, "LV already exists"));
        }

        let mut contig_area = None;
        for (pvname, areas) in self.free_areas() {
            for (start, len) in areas {
                if len >= extent_size {
                    contig_area = Some((pvname, start));
                    break;
                }
            }
        }

        // we don't support multiple segments yet
        let (pv_with_area, area_start) = match contig_area {
            None => return Err(Error::new(Other, "no contiguous area for new LV")),
            Some(x) => x,
        };

        let segment = Segment {
            name: "segment1".to_string(),
            start_extent: 0,
            extent_count: extent_size,
            ty: "striped".to_string(),
            stripes: vec![(pv_with_area, area_start)],
        };

        let mut lv = LV {
            name: name.to_string(),
            id: make_uuid(),
            status: vec!["READ".to_string(),
                         "WRITE".to_string(),
                         "VISIBLE".to_string()],
            flags: Vec::new(),
            creation_host: uname().nodename().to_string(),
            creation_time: now().to_timespec().sec,
            segments: vec![segment],
            device: None,
        };

        // poke dm and tell it about a new device
        {
            let dm = try!(DM::new(self));
            try!(dm.activate_device(&mut lv));
        }

        self.lvs.insert(name.to_string(), lv);

        self.commit()
    }

    /// Destroy a logical volume.
    pub fn lv_remove(&mut self, name: &str) -> Result<()> {
        match self.lvs.remove(name) {
            None => Err(Error::new(Other, "LV not found in VG")),
            Some(mut lv) => {
                {
                    let dm = try!(DM::new(self));
                    try!(dm.deactivate_device(&mut lv));
                }

                self.commit()
            },
        }
    }

    fn commit(&mut self) -> Result<()> {
        let map: LvmTextMap = self.clone().into();

        let mut disk_map = LvmTextMap::new();
        disk_map.insert("contents".to_string(),
                        Entry::String("Text Format Volume Group".to_string()));
        disk_map.insert("version".to_string(), Entry::Number(1));
        disk_map.insert("description".to_string(), Entry::String("".to_string()));
        disk_map.insert("creation_host".to_string(),
                        Entry::String(uname().nodename().to_string()));
        disk_map.insert("creation_time".to_string(),
                        Entry::Number(now().to_timespec().sec));
        disk_map.insert(self.name.clone(), Entry::TextMap(Box::new(map.clone())));

        // TODO: atomicity of updating pvs, metad, dm
        for pv in self.pvs.values() {
            if let Some(path) = pv.device.path() {
                let mut pvheader = PvHeader::find_in_dev(&path)
                    .expect("could not find pvheader");

                try!(pvheader.write_metadata(&disk_map));
            }
        }

        lvmetad::vg_update(&self.name, &map)
    }

    // Returns e.g. {"pv0": {0: 45, 47: 100, 100: 200} }
    // This means extents 0-44 are used, 45 and 46 are not,
    // 47-99 are used, then 100-199 are used.
    //
    // Adjacent used areas are not merged.
    //
    // PVs with no used areas are not in the outer map at all.
    //
    fn used_areas(&self) -> BTreeMap<String, BTreeMap<u64, u64>> {
        let mut used_map = BTreeMap::new();

        for lv in self.lvs.values() {
            for seg in &lv.segments {
                for &(ref pvname, start) in &seg.stripes {
                    used_map.entry(pvname.to_string())
                        .or_insert(BTreeMap::new())
                        .insert(start as u64, seg.extent_count);
                }
            }
        }

        used_map
    }

    // Returns e.g. {"pv0": {45: 47, 200: 1000} }
    // (assuming pv0 has 1000 extents)
    //
    fn free_areas(&self) -> BTreeMap<String, BTreeMap<u64, u64>> {
        let mut free_map = BTreeMap::new();

        for (pvname, mut area_map) in self.used_areas() {

            // Insert an entry to mark the end of the PV so the fold works
            // correctly
            let pv = self.pvs.get(&pvname)
                .expect("area map name refers to nonexistent PV");
            area_map.insert(pv.pe_count, 0);

            area_map.iter()
                .fold(0, |prev_end, (start, len)| {
                    if prev_end < *start {
                        free_map.entry(pvname.clone())
                            .or_insert(BTreeMap::new())
                            .insert(prev_end, start-prev_end);
                    }
                    start + len
                });
        }

        // Also return completely-unused PVs
        for (pvname, pv) in &self.pvs {
            if !free_map.contains_key(pvname) {
                let mut map = BTreeMap::new();
                map.insert(0, pv.pe_count);
                free_map.insert(pvname.clone(), map);
            }
        }

        free_map
    }
}

impl From<VG> for LvmTextMap {
    fn from(vg: VG) -> Self {
        let mut map = LvmTextMap::new();

        map.insert("id".to_string(), Entry::String(vg.id));
        map.insert("seqno".to_string(),
                   Entry::Number(vg.seqno as i64 + 1));
        map.insert("format".to_string(), Entry::String(vg.format));

        map.insert("max_pv".to_string(), Entry::Number(0));
        map.insert("max_lv".to_string(), Entry::Number(0));

        map.insert("status".to_string(),
                   Entry::List(
                       Box::new(
                           vg.status
                               .into_iter()
                               .map(|x| Entry::String(x))
                               .collect())));

        map.insert("flags".to_string(),
                   Entry::List(
                       Box::new(
                           vg.flags
                               .into_iter()
                               .map(|x| Entry::String(x))
                               .collect())));

        map.insert("extent_size".to_string(),
                   Entry::Number(vg.extent_size as i64));
        map.insert("metadata_copies".to_string(),
                   Entry::Number(vg.metadata_copies as i64));
        map.insert("physical_volumes".to_string(),
                   Entry::TextMap(
                       Box::new(
                           vg.pvs
                               .into_iter()
                               .map(|(k, v)|
                                    (k, Entry::TextMap(Box::new(v.into()))))
                               .collect())));

        if !vg.lvs.is_empty() {
            map.insert("logical_volumes".to_string(),
                       Entry::TextMap(
                           Box::new(
                               vg.lvs
                                   .into_iter()
                                   .map(|(k, v)|
                                        (k, Entry::TextMap(Box::new(v.into()))))
                                   .collect())));
        }

        map
    }
}
<commit_msg>Increment VG seqno in commit(), not in from()<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! Volume Groups

use std::io::Result;
use std::io::Error;
use std::io::ErrorKind::Other;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::str::FromStr;
use std::borrow::Cow;

use time::now;
use nix::sys::utsname::uname;

use lv::{LV, Segment};
use pv::{PV, Device};
use pvlabel::{PvHeader, SECTOR_SIZE};
use parser::{LvmTextMap, Entry};
use lvmetad;
use dm;
use dm::DM;
use util::{align_to, make_uuid};

/// A Volume Group.
#[derive(Debug, PartialEq, Clone)]
pub struct VG {
    /// Name.
    pub name: String,
    /// Uuid.
    pub id: String,
    /// The generation of metadata this VG represents.
    pub seqno: u64,
    /// Always "LVM2".
    pub format: String,
    /// Status.
    pub status: Vec<String>,
    /// Flags.
    pub flags: Vec<String>,
    /// Size of each extent, in 512-byte sectors.
    pub extent_size: u64,
    /// Maximum number of LVs, 0 means no limit.
    pub max_lv: u64,
    /// Maximum number of PVs, 0 means no limit.
    pub max_pv: u64,
    /// How many metadata copies (?)
    pub metadata_copies: u64,
    /// Physical Volumes within this volume group.
    pub pvs: BTreeMap<String, PV>,
    /// Logical Volumes within this volume group.
    pub lvs: BTreeMap<String, LV>,
}

impl VG {
    /// The total number of extents in use in the volume group.
    pub fn extents_in_use(&self) -> u64 {
        self.lvs
            .values()
            .map(|x| x.used_extents())
            .sum()
    }

    /// The total number of free extents in the volume group.
    pub fn extents_free(&self) -> u64 {
        self.extents() - self.extents_in_use()
    }

    /// The total number of extents in the volume group.
    pub fn extents(&self) -> u64 {
        self.pvs
            .values()
            .map(|x| x.pe_count)
            .sum()
    }

    // Recursively walk DM deps to see if device is present
    fn depends_on(dev: Device, dm_majors: &BTreeSet<u32>, dm: &DM) -> bool {
        if !dm_majors.contains(&dev.major) {
            return false;
        }

        if let Ok(dep_list) = dm.list_deps(dev) {
            for d in dep_list {
                if d == dev {
                    return true;
                } else if Self::depends_on(d, dm_majors, dm) {
                    return true;
                }
            }
        }

        false
    }

    /// Add a non-affiliated PV to this VG.
    pub fn add_pv(&mut self, pvh: &PvHeader) -> Result<()> {
        // Check pv is not on an LV from the vg:
        // 1) is pv's major a devicemapper major?
        // 2) Walk dm deps (equiv. of LVM2 dev_manager_device_uses_vg)
        let dm_majors = dm::dev_majors();
        let dev = try!(Device::from_str(&pvh.dev_path.to_string_lossy()));
        {
            let dm = try!(DM::new(&self));
            if Self::depends_on(dev, &dm_majors, &dm) {
                return Err(Error::new(Other, "Dependency loops prohibited"));
            }
        }

        // check pv is not already in the VG or another VG
        // Does it have text metadata??
        if let Ok(metadata) = pvh.read_metadata() {
            // Find the textmap for the vg, among all the other stuff.
            // (It's the only textmap.)
            let mut vg_name = Cow::Borrowed("<unknown>");
            for (key, value) in metadata {
                match value {
                    Entry::TextMap(_) => {
                        vg_name = Cow::Owned(key);
                        break
                    },
                    _ => {}
                }
            }

            return Err(Error::new(Other, format!("PV already in VG {}", vg_name)));
        }

        let da = try!(pvh.data_areas.get(0)
                      .ok_or(Error::new(Other, "Could not find data area in PV")));

        // figure out how many extents fit in the PV's data area
        // pe_start aligned to extent size
        let dev_size_sectors = pvh.size / SECTOR_SIZE as u64;
        let pe_start_sectors = align_to(
            (da.offset / SECTOR_SIZE as u64) as usize,
            self.extent_size as usize) as u64;
        let mda1_size_sectors = match pvh.metadata_areas.get(1) {
            Some(pvarea) => pvarea.size / SECTOR_SIZE as u64,
            None => 0,
        };
        let area_size_sectors = dev_size_sectors - pe_start_sectors - mda1_size_sectors;
        let pe_count = area_size_sectors / self.extent_size;

        let name = format!("pv{}", self.pvs.len());

        // make a PV and add it to self
        let pv = PV {
            name: name.clone(),
            id: pvh.uuid.clone(),
            device: dev,
            status: vec!["ALLOCATABLE".to_string()],
            flags: Vec::new(),
            dev_size: dev_size_sectors,
            pe_start: pe_start_sectors,
            pe_count: pe_count,
        };

        self.pvs.insert(name, pv);

        self.commit()
    }

    /// Create a new linear logical volume in the volume group.
    pub fn new_linear_lv(&mut self, name: &str, extent_size: u64) -> Result<()> {
        if self.lvs.contains_key(name) {
            return Err(Error::new(Other, "LV already exists"));
        }

        let mut contig_area = None;
        for (pvname, areas) in self.free_areas() {
            for (start, len) in areas {
                if len >= extent_size {
                    contig_area = Some((pvname, start));
                    break;
                }
            }
        }

        // we don't support multiple segments yet
        let (pv_with_area, area_start) = match contig_area {
            None => return Err(Error::new(Other, "no contiguous area for new LV")),
            Some(x) => x,
        };

        let segment = Segment {
            name: "segment1".to_string(),
            start_extent: 0,
            extent_count: extent_size,
            ty: "striped".to_string(),
            stripes: vec![(pv_with_area, area_start)],
        };

        let mut lv = LV {
            name: name.to_string(),
            id: make_uuid(),
            status: vec!["READ".to_string(),
                         "WRITE".to_string(),
                         "VISIBLE".to_string()],
            flags: Vec::new(),
            creation_host: uname().nodename().to_string(),
            creation_time: now().to_timespec().sec,
            segments: vec![segment],
            device: None,
        };

        // poke dm and tell it about a new device
        {
            let dm = try!(DM::new(self));
            try!(dm.activate_device(&mut lv));
        }

        self.lvs.insert(name.to_string(), lv);

        self.commit()
    }

    /// Destroy a logical volume.
    pub fn lv_remove(&mut self, name: &str) -> Result<()> {
        match self.lvs.remove(name) {
            None => Err(Error::new(Other, "LV not found in VG")),
            Some(mut lv) => {
                {
                    let dm = try!(DM::new(self));
                    try!(dm.deactivate_device(&mut lv));
                }

                self.commit()
            },
        }
    }

    fn commit(&mut self) -> Result<()> {
        self.seqno += 1;

        let map: LvmTextMap = self.clone().into();

        let mut disk_map = LvmTextMap::new();
        disk_map.insert("contents".to_string(),
                        Entry::String("Text Format Volume Group".to_string()));
        disk_map.insert("version".to_string(), Entry::Number(1));
        disk_map.insert("description".to_string(), Entry::String("".to_string()));
        disk_map.insert("creation_host".to_string(),
                        Entry::String(uname().nodename().to_string()));
        disk_map.insert("creation_time".to_string(),
                        Entry::Number(now().to_timespec().sec));
        disk_map.insert(self.name.clone(), Entry::TextMap(Box::new(map.clone())));

        // TODO: atomicity of updating pvs, metad, dm
        for pv in self.pvs.values() {
            if let Some(path) = pv.device.path() {
                let mut pvheader = PvHeader::find_in_dev(&path)
                    .expect("could not find pvheader");

                try!(pvheader.write_metadata(&disk_map));
            }
        }

        lvmetad::vg_update(&self.name, &map)
    }

    // Returns e.g. {"pv0": {0: 45, 47: 100, 100: 200} }
    // This means extents 0-44 are used, 45 and 46 are not,
    // 47-99 are used, then 100-199 are used.
    //
    // Adjacent used areas are not merged.
    //
    // PVs with no used areas are not in the outer map at all.
    //
    fn used_areas(&self) -> BTreeMap<String, BTreeMap<u64, u64>> {
        let mut used_map = BTreeMap::new();

        for lv in self.lvs.values() {
            for seg in &lv.segments {
                for &(ref pvname, start) in &seg.stripes {
                    used_map.entry(pvname.to_string())
                        .or_insert(BTreeMap::new())
                        .insert(start as u64, seg.extent_count);
                }
            }
        }

        used_map
    }

    // Returns e.g. {"pv0": {45: 47, 200: 1000} }
    // (assuming pv0 has 1000 extents)
    //
    fn free_areas(&self) -> BTreeMap<String, BTreeMap<u64, u64>> {
        let mut free_map = BTreeMap::new();

        for (pvname, mut area_map) in self.used_areas() {

            // Insert an entry to mark the end of the PV so the fold works
            // correctly
            let pv = self.pvs.get(&pvname)
                .expect("area map name refers to nonexistent PV");
            area_map.insert(pv.pe_count, 0);

            area_map.iter()
                .fold(0, |prev_end, (start, len)| {
                    if prev_end < *start {
                        free_map.entry(pvname.clone())
                            .or_insert(BTreeMap::new())
                            .insert(prev_end, start-prev_end);
                    }
                    start + len
                });
        }

        // Also return completely-unused PVs
        for (pvname, pv) in &self.pvs {
            if !free_map.contains_key(pvname) {
                let mut map = BTreeMap::new();
                map.insert(0, pv.pe_count);
                free_map.insert(pvname.clone(), map);
            }
        }

        free_map
    }
}

impl From<VG> for LvmTextMap {
    fn from(vg: VG) -> Self {
        let mut map = LvmTextMap::new();

        map.insert("id".to_string(), Entry::String(vg.id));
        map.insert("seqno".to_string(),
                   Entry::Number(vg.seqno as i64));
        map.insert("format".to_string(), Entry::String(vg.format));

        map.insert("max_pv".to_string(), Entry::Number(0));
        map.insert("max_lv".to_string(), Entry::Number(0));

        map.insert("status".to_string(),
                   Entry::List(
                       Box::new(
                           vg.status
                               .into_iter()
                               .map(|x| Entry::String(x))
                               .collect())));

        map.insert("flags".to_string(),
                   Entry::List(
                       Box::new(
                           vg.flags
                               .into_iter()
                               .map(|x| Entry::String(x))
                               .collect())));

        map.insert("extent_size".to_string(),
                   Entry::Number(vg.extent_size as i64));
        map.insert("metadata_copies".to_string(),
                   Entry::Number(vg.metadata_copies as i64));
        map.insert("physical_volumes".to_string(),
                   Entry::TextMap(
                       Box::new(
                           vg.pvs
                               .into_iter()
                               .map(|(k, v)|
                                    (k, Entry::TextMap(Box::new(v.into()))))
                               .collect())));

        if !vg.lvs.is_empty() {
            map.insert("logical_volumes".to_string(),
                       Entry::TextMap(
                           Box::new(
                               vg.lvs
                                   .into_iter()
                                   .map(|(k, v)|
                                        (k, Entry::TextMap(Box::new(v.into()))))
                                   .collect())));
        }

        map
    }
}
<|endoftext|>


<commit_before><commit_msg>Started on coding the chiral term.<commit_after><|endoftext|>


<commit_before>//
//  SOS: the Stupid Operating System
//  by Eliza Weisman (eliza@elizas.website)
//
//  Copyright (c) 2015-2017 Eliza Weisman
//  Released under the terms of the MIT license. See `LICENSE` in the root
//  directory of this repository for more information.
//
//! # SOS Intrusive Collections
//!
//! _Intrusive_ data structures are data structures whose elements are
//! "aware" of the structures in which they are stored. That is to say
//! that data related to the layout and structure of an intrusive collection
//! is stored by the elements in the collection, rather than internally to
//! them.
//!
//! Intrusive data structures are useful for low-level programming in Rust
//! since they do not explicitly allocate memory. This means that we can use
//! intrusive structures to implement the kernel memory allocator and other
//! kernel subsystems which require structures such as lists prior to
//! the initialization of the kernel heap.
//!
//! This crate currently provides an intrusive linked-list implementation.
//!
//! # Features
//! + `use-std`: use the Rust standard library (`std`), rather than `core`.
#![crate_name = "sos_intrusive"]
#![crate_type = "lib"]
#![feature( const_fn
          , const_ptr_null_mut )]
#![feature(unique )]
#![cfg_attr(not(feature = "use-std"), no_std )]
#![cfg_attr(feature = "clippy", feature(plugin))]
#![cfg_attr(feature = "clippy", plugin(clippy))]

#![cfg_attr(test, feature(box_syntax))]

pub mod rawlink;
pub use rawlink::RawLink;
pub mod list;
pub use list::List;

#[cfg(test)]
extern crate std;
<commit_msg>fix(sos_intrusive): temp fix for #111<commit_after>//
//  SOS: the Stupid Operating System
//  by Eliza Weisman (eliza@elizas.website)
//
//  Copyright (c) 2015-2017 Eliza Weisman
//  Released under the terms of the MIT license. See `LICENSE` in the root
//  directory of this repository for more information.
//
//! # SOS Intrusive Collections
//!
//! _Intrusive_ data structures are data structures whose elements are
//! "aware" of the structures in which they are stored. That is to say
//! that data related to the layout and structure of an intrusive collection
//! is stored by the elements in the collection, rather than internally to
//! them.
//!
//! Intrusive data structures are useful for low-level programming in Rust
//! since they do not explicitly allocate memory. This means that we can use
//! intrusive structures to implement the kernel memory allocator and other
//! kernel subsystems which require structures such as lists prior to
//! the initialization of the kernel heap.
//!
//! This crate currently provides an intrusive linked-list implementation.
//!
//! # Features
//! + `use-std`: use the Rust standard library (`std`), rather than `core`.
#![crate_name = "sos_intrusive"]
#![crate_type = "lib"]
#![feature( const_fn
          , const_ptr_null_mut )]
#![feature(ptr_internals)]
#![feature(unique )]
#![cfg_attr(not(feature = "use-std"), no_std )]
#![cfg_attr(feature = "clippy", feature(plugin))]
#![cfg_attr(feature = "clippy", plugin(clippy))]

#![cfg_attr(test, feature(box_syntax))]

pub mod rawlink;
pub use rawlink::RawLink;
pub mod list;
pub use list::List;

#[cfg(test)]
extern crate std;
<|endoftext|>


<commit_before><commit_msg>shapes.rs: Use property_bag::length_or_none() in a spot that was missing<commit_after><|endoftext|>


<commit_before>//! # The Rust core allocation and collections library
//!
//! This library provides smart pointers and collections for managing
//! heap-allocated values.
//!
//! This library, like libcore, normally doesn’t need to be used directly
//! since its contents are re-exported in the [`std` crate](../std/index.html).
//! Crates that use the `#![no_std]` attribute however will typically
//! not depend on `std`, so they’d use this crate instead.
//!
//! ## Boxed values
//!
//! The [`Box`] type is a smart pointer type. There can only be one owner of a
//! [`Box`], and the owner can decide to mutate the contents, which live on the
//! heap.
//!
//! This type can be sent among threads efficiently as the size of a `Box` value
//! is the same as that of a pointer. Tree-like data structures are often built
//! with boxes because each node often has only one owner, the parent.
//!
//! ## Reference counted pointers
//!
//! The [`Rc`] type is a non-threadsafe reference-counted pointer type intended
//! for sharing memory within a thread. An [`Rc`] pointer wraps a type, `T`, and
//! only allows access to `&T`, a shared reference.
//!
//! This type is useful when inherited mutability (such as using [`Box`]) is too
//! constraining for an application, and is often paired with the [`Cell`] or
//! [`RefCell`] types in order to allow mutation.
//!
//! ## Atomically reference counted pointers
//!
//! The [`Arc`] type is the threadsafe equivalent of the [`Rc`] type. It
//! provides all the same functionality of [`Rc`], except it requires that the
//! contained type `T` is shareable. Additionally, [`Arc<T>`][`Arc`] is itself
//! sendable while [`Rc<T>`][`Rc`] is not.
//!
//! This type allows for shared access to the contained data, and is often
//! paired with synchronization primitives such as mutexes to allow mutation of
//! shared resources.
//!
//! ## Collections
//!
//! Implementations of the most common general purpose data structures are
//! defined in this library. They are re-exported through the
//! [standard collections library](../std/collections/index.html).
//!
//! ## Heap interfaces
//!
//! The [`alloc`](alloc/index.html) module defines the low-level interface to the
//! default global allocator. It is not compatible with the libc allocator API.
//!
//! [`Arc`]: sync
//! [`Box`]: boxed
//! [`Cell`]: core::cell
//! [`Rc`]: rc
//! [`RefCell`]: core::cell

// To run liballoc tests without x.py without ending up with two copies of liballoc, Miri needs to be
// able to "empty" this crate. See <https://github.com/rust-lang/miri-test-libstd/issues/4>.
// rustc itself never sets the feature, so this line has no affect there.
#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))]
#![allow(unused_attributes)]
#![stable(feature = "alloc", since = "1.36.0")]
#![doc(
    html_playground_url = "https://play.rust-lang.org/",
    issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/",
    test(no_crate_inject, attr(allow(unused_variables), deny(warnings)))
)]
#![cfg_attr(
    not(bootstrap),
    doc(cfg_hide(
        not(test),
        not(any(test, bootstrap)),
        any(not(feature = "miri-test-libstd"), test, doctest),
        no_global_oom_handling,
        not(no_global_oom_handling),
        target_has_atomic = "ptr"
    ))
)]
#![no_std]
#![needs_allocator]
//
// Lints:
#![deny(unsafe_op_in_unsafe_fn)]
#![warn(deprecated_in_future)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![allow(explicit_outlives_requirements)]
//
// Library features:
#![feature(alloc_layout_extra)]
#![feature(allocator_api)]
#![feature(array_chunks)]
#![feature(array_methods)]
#![feature(array_windows)]
#![feature(async_stream)]
#![feature(coerce_unsized)]
#![cfg_attr(not(no_global_oom_handling), feature(const_btree_new))]
#![feature(const_cow_is_borrowed)]
#![feature(core_intrinsics)]
#![feature(cow_is_borrowed)]
#![feature(dispatch_from_dyn)]
#![feature(exact_size_is_empty)]
#![feature(extend_one)]
#![feature(fmt_internals)]
#![feature(fn_traits)]
#![feature(inplace_iteration)]
#![feature(iter_advance_by)]
#![feature(iter_zip)]
#![feature(layout_for_ptr)]
#![feature(maybe_uninit_extra)]
#![feature(maybe_uninit_slice)]
#![feature(maybe_uninit_uninit_array)]
#![cfg_attr(test, feature(new_uninit))]
#![feature(nonnull_slice_from_raw_parts)]
#![feature(option_result_unwrap_unchecked)]
#![feature(pattern)]
#![feature(ptr_internals)]
#![feature(receiver_trait)]
#![feature(set_ptr_value)]
#![feature(slice_group_by)]
#![feature(slice_partition_dedup)]
#![feature(slice_ptr_get)]
#![feature(slice_ptr_len)]
#![feature(slice_range)]
#![feature(str_internals)]
#![feature(trusted_len)]
#![feature(trusted_random_access)]
#![feature(try_trait_v2)]
#![feature(unicode_internals)]
#![feature(unsize)]
//
// Language features:
#![feature(allocator_internals)]
#![feature(allow_internal_unstable)]
#![feature(arbitrary_self_types)]
#![feature(associated_type_bounds)]
#![feature(auto_traits)]
#![feature(box_patterns)]
#![feature(box_syntax)]
#![feature(cfg_sanitize)]
#![feature(cfg_target_has_atomic)]
#![feature(const_fn_trait_bound)]
#![feature(const_trait_impl)]
#![feature(decl_macro)]
#![feature(destructuring_assignment)]
#![feature(dropck_eyepatch)]
#![feature(exclusive_range_pattern)]
#![feature(fundamental)]
#![cfg_attr(not(test), feature(generator_trait))]
#![feature(lang_items)]
#![feature(min_specialization)]
#![feature(negative_impls)]
#![feature(never_type)]
#![feature(nll)]
#![feature(rustc_allow_const_fn_unstable)]
#![feature(rustc_attrs)]
#![feature(staged_api)]
#![cfg_attr(test, feature(test))]
#![feature(unboxed_closures)]
#![feature(unsized_fn_params)]
//
// Rustdoc features:
#![feature(doc_cfg)]
#![cfg_attr(not(bootstrap), feature(doc_cfg_hide))]
// Technically, this is a bug in rustdoc: rustdoc sees the documentation on `#[lang = slice_alloc]`
// blocks is for `&[T]`, which also has documentation using this feature in `core`, and gets mad
// that the feature-gate isn't enabled. Ideally, it wouldn't check for the feature gate for docs
// from other crates, but since this can only appear for lang items, it doesn't seem worth fixing.
#![feature(intra_doc_pointers)]

// Allow testing this library
#[cfg(test)]
#[macro_use]
extern crate std;
#[cfg(test)]
extern crate test;

// Module with internal macros used by other modules (needs to be included before other modules).
#[macro_use]
mod macros;

// Heaps provided for low-level allocation strategies

pub mod alloc;

// Primitive types using the heaps above

// Need to conditionally define the mod from `boxed.rs` to avoid
// duplicating the lang-items when building in test cfg; but also need
// to allow code to have `use boxed::Box;` declarations.
#[cfg(not(test))]
pub mod boxed;
#[cfg(test)]
mod boxed {
    pub use std::boxed::Box;
}
pub mod borrow;
pub mod collections;
pub mod fmt;
pub mod raw_vec;
pub mod rc;
pub mod slice;
pub mod str;
pub mod string;
#[cfg(target_has_atomic = "ptr")]
pub mod sync;
#[cfg(all(not(no_global_oom_handling), target_has_atomic = "ptr"))]
pub mod task;
#[cfg(test)]
mod tests;
pub mod vec;

#[doc(hidden)]
#[unstable(feature = "liballoc_internals", issue = "none", reason = "implementation detail")]
pub mod __export {
    pub use core::format_args;
}
<commit_msg>Remove unused library #![feature]s from alloc.<commit_after>//! # The Rust core allocation and collections library
//!
//! This library provides smart pointers and collections for managing
//! heap-allocated values.
//!
//! This library, like libcore, normally doesn’t need to be used directly
//! since its contents are re-exported in the [`std` crate](../std/index.html).
//! Crates that use the `#![no_std]` attribute however will typically
//! not depend on `std`, so they’d use this crate instead.
//!
//! ## Boxed values
//!
//! The [`Box`] type is a smart pointer type. There can only be one owner of a
//! [`Box`], and the owner can decide to mutate the contents, which live on the
//! heap.
//!
//! This type can be sent among threads efficiently as the size of a `Box` value
//! is the same as that of a pointer. Tree-like data structures are often built
//! with boxes because each node often has only one owner, the parent.
//!
//! ## Reference counted pointers
//!
//! The [`Rc`] type is a non-threadsafe reference-counted pointer type intended
//! for sharing memory within a thread. An [`Rc`] pointer wraps a type, `T`, and
//! only allows access to `&T`, a shared reference.
//!
//! This type is useful when inherited mutability (such as using [`Box`]) is too
//! constraining for an application, and is often paired with the [`Cell`] or
//! [`RefCell`] types in order to allow mutation.
//!
//! ## Atomically reference counted pointers
//!
//! The [`Arc`] type is the threadsafe equivalent of the [`Rc`] type. It
//! provides all the same functionality of [`Rc`], except it requires that the
//! contained type `T` is shareable. Additionally, [`Arc<T>`][`Arc`] is itself
//! sendable while [`Rc<T>`][`Rc`] is not.
//!
//! This type allows for shared access to the contained data, and is often
//! paired with synchronization primitives such as mutexes to allow mutation of
//! shared resources.
//!
//! ## Collections
//!
//! Implementations of the most common general purpose data structures are
//! defined in this library. They are re-exported through the
//! [standard collections library](../std/collections/index.html).
//!
//! ## Heap interfaces
//!
//! The [`alloc`](alloc/index.html) module defines the low-level interface to the
//! default global allocator. It is not compatible with the libc allocator API.
//!
//! [`Arc`]: sync
//! [`Box`]: boxed
//! [`Cell`]: core::cell
//! [`Rc`]: rc
//! [`RefCell`]: core::cell

// To run liballoc tests without x.py without ending up with two copies of liballoc, Miri needs to be
// able to "empty" this crate. See <https://github.com/rust-lang/miri-test-libstd/issues/4>.
// rustc itself never sets the feature, so this line has no affect there.
#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))]
#![allow(unused_attributes)]
#![stable(feature = "alloc", since = "1.36.0")]
#![doc(
    html_playground_url = "https://play.rust-lang.org/",
    issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/",
    test(no_crate_inject, attr(allow(unused_variables), deny(warnings)))
)]
#![cfg_attr(
    not(bootstrap),
    doc(cfg_hide(
        not(test),
        not(any(test, bootstrap)),
        any(not(feature = "miri-test-libstd"), test, doctest),
        no_global_oom_handling,
        not(no_global_oom_handling),
        target_has_atomic = "ptr"
    ))
)]
#![no_std]
#![needs_allocator]
//
// Lints:
#![deny(unsafe_op_in_unsafe_fn)]
#![warn(deprecated_in_future)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![allow(explicit_outlives_requirements)]
//
// Library features:
#![feature(alloc_layout_extra)]
#![feature(allocator_api)]
#![feature(array_chunks)]
#![feature(array_methods)]
#![feature(array_windows)]
#![feature(async_stream)]
#![feature(coerce_unsized)]
#![cfg_attr(not(no_global_oom_handling), feature(const_btree_new))]
#![feature(const_cow_is_borrowed)]
#![feature(core_intrinsics)]
#![feature(dispatch_from_dyn)]
#![feature(exact_size_is_empty)]
#![feature(extend_one)]
#![feature(fmt_internals)]
#![feature(fn_traits)]
#![feature(inplace_iteration)]
#![feature(iter_advance_by)]
#![feature(iter_zip)]
#![feature(layout_for_ptr)]
#![feature(maybe_uninit_extra)]
#![feature(maybe_uninit_slice)]
#![cfg_attr(test, feature(new_uninit))]
#![feature(nonnull_slice_from_raw_parts)]
#![feature(option_result_unwrap_unchecked)]
#![feature(pattern)]
#![feature(ptr_internals)]
#![feature(receiver_trait)]
#![feature(set_ptr_value)]
#![feature(slice_group_by)]
#![feature(slice_ptr_get)]
#![feature(slice_ptr_len)]
#![feature(slice_range)]
#![feature(str_internals)]
#![feature(trusted_len)]
#![feature(trusted_random_access)]
#![feature(try_trait_v2)]
#![feature(unicode_internals)]
#![feature(unsize)]
//
// Language features:
#![feature(allocator_internals)]
#![feature(allow_internal_unstable)]
#![feature(arbitrary_self_types)]
#![feature(associated_type_bounds)]
#![feature(auto_traits)]
#![feature(box_patterns)]
#![feature(box_syntax)]
#![feature(cfg_sanitize)]
#![feature(cfg_target_has_atomic)]
#![feature(const_fn_trait_bound)]
#![feature(const_trait_impl)]
#![feature(decl_macro)]
#![feature(destructuring_assignment)]
#![feature(dropck_eyepatch)]
#![feature(exclusive_range_pattern)]
#![feature(fundamental)]
#![cfg_attr(not(test), feature(generator_trait))]
#![feature(lang_items)]
#![feature(min_specialization)]
#![feature(negative_impls)]
#![feature(never_type)]
#![feature(nll)]
#![feature(rustc_allow_const_fn_unstable)]
#![feature(rustc_attrs)]
#![feature(staged_api)]
#![cfg_attr(test, feature(test))]
#![feature(unboxed_closures)]
#![feature(unsized_fn_params)]
//
// Rustdoc features:
#![feature(doc_cfg)]
#![cfg_attr(not(bootstrap), feature(doc_cfg_hide))]
// Technically, this is a bug in rustdoc: rustdoc sees the documentation on `#[lang = slice_alloc]`
// blocks is for `&[T]`, which also has documentation using this feature in `core`, and gets mad
// that the feature-gate isn't enabled. Ideally, it wouldn't check for the feature gate for docs
// from other crates, but since this can only appear for lang items, it doesn't seem worth fixing.
#![feature(intra_doc_pointers)]

// Allow testing this library
#[cfg(test)]
#[macro_use]
extern crate std;
#[cfg(test)]
extern crate test;

// Module with internal macros used by other modules (needs to be included before other modules).
#[macro_use]
mod macros;

// Heaps provided for low-level allocation strategies

pub mod alloc;

// Primitive types using the heaps above

// Need to conditionally define the mod from `boxed.rs` to avoid
// duplicating the lang-items when building in test cfg; but also need
// to allow code to have `use boxed::Box;` declarations.
#[cfg(not(test))]
pub mod boxed;
#[cfg(test)]
mod boxed {
    pub use std::boxed::Box;
}
pub mod borrow;
pub mod collections;
pub mod fmt;
pub mod raw_vec;
pub mod rc;
pub mod slice;
pub mod str;
pub mod string;
#[cfg(target_has_atomic = "ptr")]
pub mod sync;
#[cfg(all(not(no_global_oom_handling), target_has_atomic = "ptr"))]
pub mod task;
#[cfg(test)]
mod tests;
pub mod vec;

#[doc(hidden)]
#[unstable(feature = "liballoc_internals", issue = "none", reason = "implementation detail")]
pub mod __export {
    pub use core::format_args;
}
<|endoftext|>


<commit_before>use scraping::{Html, Selector};
use scraping::element_ref::ElementRef;
use hyper::Client;
use hyper::header::Connection;
use hyper::header::ConnectionOption;
use std::io::Read;
use std::error;
use std::fmt;
use regex::Regex;

static BASE_URL:  &'static str = "http://tools.applemusic.com/embed/v1/";
static MUSIC_URL: &'static str = r#"musicUrl = "([\x00-\x21\x23-\x7F]+)""#; // except \x22(")

#[derive(Debug, Clone, RustcDecodable, RustcEncodable)]
pub struct Song {
    pub id:          String,
    pub country:     String,
    pub title:       String,
    pub artwork_url: String,
    pub artist:      String,
    pub audio_url:   String,
    pub music_url:   String,
}

#[derive(Debug, Clone, RustcDecodable, RustcEncodable)]
pub struct Album {
    pub id:           String,
    pub country:      String,
    pub title:        String,
    pub artwork_url:  String,
    pub album_artist: String,
    pub music_url:    String,
    pub genre:        String,
    pub tracks:       Option<Vec<Track>>,
}

#[derive(Debug, Clone, RustcDecodable, RustcEncodable)]
pub struct Playlist {
    pub id:          String,
    pub country:     String,
    pub title:       String,
    pub curator:     String,
    pub description: String,
    pub artwork_url: String,
    pub music_url:   String,
    pub count:       String,
}

#[derive(Debug, Clone, RustcDecodable, RustcEncodable)]
pub struct Track {
    pub title:       String,
    pub artwork_url: String,
    pub artist:      String,
    pub audio_url:   String,
}

#[derive(Debug)]
pub struct ScrapeError {
    reason: String,
}

impl fmt::Display for ScrapeError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.reason)
    }
}

type ScrapeResult<T> = Result<T, ScrapeError>;

impl error::Error for ScrapeError {
    fn description(&self) -> &str {
        &self.reason
    }
}

pub fn fetch_song(id: &str, country: &str) -> ScrapeResult<Song> {
    let client = Client::new();
    let url = format!("{}/song/{}?country={}", BASE_URL, id, country);
    let mut res = client.get(&url)
        .header(Connection(vec![ConnectionOption::Close]))
        .send()
        .unwrap();
    if !res.status.is_success() {
        return Err(ScrapeError { reason: "network error".to_string() })
    }
    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();
    let fragment = Html::parse_fragment(&body);

    let artwork_url = try!(extract_artwork_url(fragment.clone()));
    let title       = try!(extract_song_title(fragment.clone()));
    let artist      = try!(extract_song_artist(fragment.clone()));
    let audio_url   = try!(extract_audio_url(fragment.clone()));
    let music_url   = try!(extract_music_url(fragment.clone()));
    Ok(Song {
        id:          id.to_string(),
        country:     country.to_string(),
        title:       title,
        artist:      artist,
        artwork_url: artwork_url,
        audio_url:   audio_url,
        music_url:   music_url,
    })
}

pub fn fetch_album(id: &str, country: &str) -> ScrapeResult<Album> {
    let client = Client::new();
    let url = format!("{}/album/{}?country={}", BASE_URL, id, country);
    let mut res = client.get(&url)
        .header(Connection(vec![ConnectionOption::Close]))
        .send()
        .unwrap();
    if !res.status.is_success() {
        return Err(ScrapeError { reason: "network error".to_string() })
    }
    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();
    let fragment = Html::parse_fragment(&body);

    let artwork_url  = try!(extract_artwork_url(fragment.clone()));
    let title        = try!(extract_title(fragment.clone()));
    let album_artist = try!(extract_album_artist(fragment.clone()));
    let count        = try!(extract_count(fragment.clone()));
    let music_url    = try!(extract_music_url(fragment.clone()));
    let mut tracks   = Vec::new();
    let tracks_selector = Selector::parse(".track").unwrap();
    for node in fragment.select(&tracks_selector) {
        tracks.push(extract_track(node));
    }
    Ok(Album {
        id:           id.to_string(),
        country:      country.to_string(),
        title:        title,
        album_artist: album_artist,
        artwork_url:  artwork_url,
        music_url:    music_url,
        genre:        count,
        tracks:       Some(tracks),
    })
}

pub fn fetch_playlist(id: &str, country: &str) -> ScrapeResult<Playlist> {
    let client = Client::new();
    let url = format!("{}/playlist/pl.{}?country={}", BASE_URL, id, country);
    let mut res = client.get(&url)
        .header(Connection(vec![ConnectionOption::Close]))
        .send()
        .unwrap();
    if !res.status.is_success() {
        return Err(ScrapeError { reason: "network error".to_string() })
    }
    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();
    let fragment = Html::parse_fragment(&body);

    let artwork_url = try!(extract_artwork_url(fragment.clone()));
    let title       = try!(extract_title(fragment.clone()));
    let description = try!(extract_description(fragment.clone()));
    let curator     = try!(extract_curator(fragment.clone()));
    let count       = try!(extract_count(fragment.clone()));
    let music_url   = try!(extract_music_url(fragment.clone()));
    let mut tracks  = Vec::new();
    let tracks_selector = Selector::parse(".track").unwrap();
    for node in fragment.select(&tracks_selector) {
        let track = extract_track(node);
        tracks.push(track);
    }
    Ok(Playlist {
        id:          id.to_string(),
        country:     country.to_string(),
        title:       title,
        curator:     curator,
        description: description,
        artwork_url: artwork_url,
        music_url:   music_url,
        count:       count,
    })
}

fn extract_music_url(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse("script").unwrap();
    html.select(&selector)
        .last()
        .and_then(|img| img.text().next())
        .and_then(|script| match Regex::new(MUSIC_URL) {
            Ok(re) => match re.captures(script) {
                Some(cap) => Some(cap[1].to_string()),
                None => None
            },
            Err(_) => None
        })
        .ok_or(ScrapeError { reason: "music url is not found".to_string() })
}

fn extract_artwork_url(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse("#heroArtImage > img").unwrap();
    html.select(&selector)
        .next()
        .and_then(|img| img.value().attr("src"))
        .map(|url| url.trim().to_string())
        .ok_or(ScrapeError { reason: "artwork url is not found".to_string() })
}

fn extract_audio_url(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse("#heroArtImage > .song-audio").unwrap();
    html.select(&selector)
        .next()
        .and_then(|img| img.value().attr("data-url"))
        .map(|url| url.trim().to_string())
        .ok_or(ScrapeError { reason: "audio url is not found".to_string() })
}

fn extract_description(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse("#description").unwrap();
    html.select(&selector)
        .next()
        .and_then(|div| div.text().next())
        .map(|text| text.trim().to_string())
        .or(Some("".to_string()))
        .ok_or(ScrapeError { reason: "description is not found".to_string() })
}

fn extract_title(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse(".heroMeta > .title > a").unwrap();
    html.select(&selector)
        .next()
        .and_then(|a| a.text().next())
        .map(|text| text.trim().to_string())
        .ok_or(ScrapeError { reason: "title is not found".to_string() })
}

fn extract_song_title(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse(".heroMeta > .details > .title-explicit > .title > a").unwrap();
    html.select(&selector)
        .next()
        .and_then(|a| a.text().next())
        .map(|text| text.trim().to_string())
        .ok_or(ScrapeError { reason: "title is not found".to_string() })
}

fn extract_curator(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse(".heroMeta > .curator > a").unwrap();
    html.select(&selector)
        .next()
        .and_then(|a| a.text().next())
        .map(|text| text.trim().to_string())
        .ok_or(ScrapeError { reason: "curator is not found".to_string() })
}

fn extract_album_artist(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse(".heroMeta > .album-artist > a").unwrap();
    html.select(&selector)
        .next()
        .and_then(|a| a.text().next())
        .map(|text| text.trim().to_string())
        .ok_or(ScrapeError { reason: "album artist is not found".to_string() })
}

fn extract_song_artist(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse(".heroMeta > .details > .song-artist > a").unwrap();
    html.select(&selector)
        .next()
        .and_then(|a| a.text().next())
        .map(|text| text.trim().to_string())
        .ok_or(ScrapeError { reason: "song artist is not found".to_string() })
}

fn extract_count(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse(".heroMeta > .count").unwrap();
    html.select(&selector)
        .next()
        .and_then(|a| a.text().next())
        .map(|text| text.trim().to_string())
        .ok_or(ScrapeError { reason: "count is not found".to_string() })
}

fn extract_track(node: ElementRef) -> Track {
    let img_selector    = Selector::parse(".artworkImage > img").unwrap();
    let title_selector  = Selector::parse(".title").unwrap();
    let artist_selector = Selector::parse(".artist").unwrap();
    let audio_selector  = Selector::parse(".playlist-audio").unwrap();

    let mut title:       String = "".to_string();
    let mut artwork_url: String = "".to_string();
    let mut artist:      String = "".to_string();
    let mut audio_url:   String = "".to_string();
    if let Some(n) = node.select(&title_selector).next() {
        if let Some(text) = n.text().next() {
            title = text.trim().to_string();
        }
    }

    if let Some(n) = node.select(&artist_selector).next() {
        if let Some(text) = n.text().next() {
            artist = text.trim().to_string();
        }
    }

    if let Some(n) = node.select(&img_selector).next() {
        if let Some(url) = n.value().attr("src") {
            artwork_url = url.trim().to_string();
        }
    }

    if let Some(n) = node.select(&audio_selector).next() {
        if let Some(url) = n.value().attr("data-url") {
            audio_url = url.trim().to_string();
        }
    }

    Track {
        title:       title,
        artwork_url: artwork_url,
        artist:      artist,
        audio_url:   audio_url,
    }
}

#[cfg(test)]
mod test {
    use super::fetch_playlist;
    use super::fetch_album;
    use super::fetch_song;
    #[test]
    fn test_fetch_playlist() {
        let playlist = fetch_playlist("pl.2ff0e502db0c44a598a7cb2261a5e6b2", "jp").unwrap();
        assert_eq!(playlist.id, "pl.2ff0e502db0c44a598a7cb2261a5e6b2");
    }
    #[test]
    fn test_fetch_album() {
        let album = fetch_album("1160715126", "jp").unwrap();
        assert_eq!(album.id, "1160715126");
    }
    #[test]
    fn test_fetch_song() {
        let song = fetch_song("1160715431", "jp").unwrap();
        assert_eq!(song.id, "1160715431");
    }
}
<commit_msg>Refactoring. Impl apple_music::parse_url_as_album and apple_music::parse_url_as_playlist from scraper<commit_after>use scraping::{Html, Selector};
use scraping::element_ref::ElementRef;
use hyper::Client;
use hyper::header::Connection;
use hyper::header::ConnectionOption;
use std::io::Read;
use std::error;
use std::fmt;
use regex::Regex;

static BASE_URL:  &'static str = "http://tools.applemusic.com/embed/v1/";
static MUSIC_URL: &'static str = r#"musicUrl = "([\x00-\x21\x23-\x7F]+)""#; // except \x22(")

static ALBUM_LINK:    &'static str = r"itunes.apple.com/([a-zA-Z0-9_-]+)/album/([a-zA-Z0-9_-]+)/id([a-zA-Z0-9_-]+)";
static PLAYLIST_LINK: &'static str = r"itunes.apple.com/([a-zA-Z0-9_-]+)/playlist/([a-zA-Z0-9_-]+)/idpl.([a-zA-Z0-9_-]+)";

#[derive(Debug, Clone, RustcDecodable, RustcEncodable)]
pub struct Song {
    pub id:          String,
    pub country:     String,
    pub title:       String,
    pub artwork_url: String,
    pub artist:      String,
    pub audio_url:   String,
    pub music_url:   String,
}

#[derive(Debug, Clone, RustcDecodable, RustcEncodable)]
pub struct Album {
    pub id:           String,
    pub country:      String,
    pub title:        String,
    pub artwork_url:  String,
    pub album_artist: String,
    pub music_url:    String,
    pub genre:        String,
    pub tracks:       Option<Vec<Track>>,
}

#[derive(Debug, Clone, RustcDecodable, RustcEncodable)]
pub struct Playlist {
    pub id:          String,
    pub country:     String,
    pub title:       String,
    pub curator:     String,
    pub description: String,
    pub artwork_url: String,
    pub music_url:   String,
    pub count:       String,
}

#[derive(Debug, Clone, RustcDecodable, RustcEncodable)]
pub struct Track {
    pub title:       String,
    pub artwork_url: String,
    pub artist:      String,
    pub audio_url:   String,
}

#[derive(Debug)]
pub struct ScrapeError {
    reason: String,
}

impl fmt::Display for ScrapeError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.reason)
    }
}

type ScrapeResult<T> = Result<T, ScrapeError>;

impl error::Error for ScrapeError {
    fn description(&self) -> &str {
        &self.reason
    }
}

pub fn parse_url_as_album(value: &str) -> Option<(String, String, String, Option<String>)> {
    parse_url(&value, ALBUM_LINK)
}

pub fn parse_url_as_playlist(value: &str) -> Option<(String, String, String, Option<String>)> {
    parse_url(&value, PLAYLIST_LINK)
}

pub fn parse_url(value: &str, regex_str: &str) -> Option<(String, String, String, Option<String>)> {
    match Regex::new(regex_str) {
        Ok(re) => match re.captures(value) {
            Some(cap) => {
                let country: String = cap[1].to_string();
                let name:    String = cap[2].to_string();
                let id:      String = cap[3].to_string();
                return Some((country, name, id, url_param(value, "i")));
            },
            None => None
        },
        Err(_) => None
    }
}

pub fn fetch_song(id: &str, country: &str) -> ScrapeResult<Song> {
    let client = Client::new();
    let url = format!("{}/song/{}?country={}", BASE_URL, id, country);
    let mut res = client.get(&url)
        .header(Connection(vec![ConnectionOption::Close]))
        .send()
        .unwrap();
    if !res.status.is_success() {
        return Err(ScrapeError { reason: "network error".to_string() })
    }
    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();
    let fragment = Html::parse_fragment(&body);

    let artwork_url = try!(extract_artwork_url(fragment.clone()));
    let title       = try!(extract_song_title(fragment.clone()));
    let artist      = try!(extract_song_artist(fragment.clone()));
    let audio_url   = try!(extract_audio_url(fragment.clone()));
    let music_url   = try!(extract_music_url(fragment.clone()));
    Ok(Song {
        id:          id.to_string(),
        country:     country.to_string(),
        title:       title,
        artist:      artist,
        artwork_url: artwork_url,
        audio_url:   audio_url,
        music_url:   music_url,
    })
}

pub fn fetch_album(id: &str, country: &str) -> ScrapeResult<Album> {
    let client = Client::new();
    let url = format!("{}/album/{}?country={}", BASE_URL, id, country);
    let mut res = client.get(&url)
        .header(Connection(vec![ConnectionOption::Close]))
        .send()
        .unwrap();
    if !res.status.is_success() {
        return Err(ScrapeError { reason: "network error".to_string() })
    }
    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();
    let fragment = Html::parse_fragment(&body);

    let artwork_url  = try!(extract_artwork_url(fragment.clone()));
    let title        = try!(extract_title(fragment.clone()));
    let album_artist = try!(extract_album_artist(fragment.clone()));
    let count        = try!(extract_count(fragment.clone()));
    let music_url    = try!(extract_music_url(fragment.clone()));
    let mut tracks   = Vec::new();
    let tracks_selector = Selector::parse(".track").unwrap();
    for node in fragment.select(&tracks_selector) {
        tracks.push(extract_track(node));
    }
    Ok(Album {
        id:           id.to_string(),
        country:      country.to_string(),
        title:        title,
        album_artist: album_artist,
        artwork_url:  artwork_url,
        music_url:    music_url,
        genre:        count,
        tracks:       Some(tracks),
    })
}

pub fn fetch_playlist(id: &str, country: &str) -> ScrapeResult<Playlist> {
    let client = Client::new();
    let url = format!("{}/playlist/pl.{}?country={}", BASE_URL, id, country);
    let mut res = client.get(&url)
        .header(Connection(vec![ConnectionOption::Close]))
        .send()
        .unwrap();
    if !res.status.is_success() {
        return Err(ScrapeError { reason: "network error".to_string() })
    }
    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();
    let fragment = Html::parse_fragment(&body);

    let artwork_url = try!(extract_artwork_url(fragment.clone()));
    let title       = try!(extract_title(fragment.clone()));
    let description = try!(extract_description(fragment.clone()));
    let curator     = try!(extract_curator(fragment.clone()));
    let count       = try!(extract_count(fragment.clone()));
    let music_url   = try!(extract_music_url(fragment.clone()));
    let mut tracks  = Vec::new();
    let tracks_selector = Selector::parse(".track").unwrap();
    for node in fragment.select(&tracks_selector) {
        let track = extract_track(node);
        tracks.push(track);
    }
    Ok(Playlist {
        id:          id.to_string(),
        country:     country.to_string(),
        title:       title,
        curator:     curator,
        description: description,
        artwork_url: artwork_url,
        music_url:   music_url,
        count:       count,
    })
}

fn extract_music_url(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse("script").unwrap();
    html.select(&selector)
        .last()
        .and_then(|img| img.text().next())
        .and_then(|script| match Regex::new(MUSIC_URL) {
            Ok(re) => match re.captures(script) {
                Some(cap) => Some(cap[1].to_string()),
                None => None
            },
            Err(_) => None
        })
        .ok_or(ScrapeError { reason: "music url is not found".to_string() })
}

fn extract_artwork_url(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse("#heroArtImage > img").unwrap();
    html.select(&selector)
        .next()
        .and_then(|img| img.value().attr("src"))
        .map(|url| url.trim().to_string())
        .ok_or(ScrapeError { reason: "artwork url is not found".to_string() })
}

fn extract_audio_url(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse("#heroArtImage > .song-audio").unwrap();
    html.select(&selector)
        .next()
        .and_then(|img| img.value().attr("data-url"))
        .map(|url| url.trim().to_string())
        .ok_or(ScrapeError { reason: "audio url is not found".to_string() })
}

fn extract_description(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse("#description").unwrap();
    html.select(&selector)
        .next()
        .and_then(|div| div.text().next())
        .map(|text| text.trim().to_string())
        .or(Some("".to_string()))
        .ok_or(ScrapeError { reason: "description is not found".to_string() })
}

fn extract_title(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse(".heroMeta > .title > a").unwrap();
    html.select(&selector)
        .next()
        .and_then(|a| a.text().next())
        .map(|text| text.trim().to_string())
        .ok_or(ScrapeError { reason: "title is not found".to_string() })
}

fn extract_song_title(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse(".heroMeta > .details > .title-explicit > .title > a").unwrap();
    html.select(&selector)
        .next()
        .and_then(|a| a.text().next())
        .map(|text| text.trim().to_string())
        .ok_or(ScrapeError { reason: "title is not found".to_string() })
}

fn extract_curator(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse(".heroMeta > .curator > a").unwrap();
    html.select(&selector)
        .next()
        .and_then(|a| a.text().next())
        .map(|text| text.trim().to_string())
        .ok_or(ScrapeError { reason: "curator is not found".to_string() })
}

fn extract_album_artist(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse(".heroMeta > .album-artist > a").unwrap();
    html.select(&selector)
        .next()
        .and_then(|a| a.text().next())
        .map(|text| text.trim().to_string())
        .ok_or(ScrapeError { reason: "album artist is not found".to_string() })
}

fn extract_song_artist(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse(".heroMeta > .details > .song-artist > a").unwrap();
    html.select(&selector)
        .next()
        .and_then(|a| a.text().next())
        .map(|text| text.trim().to_string())
        .ok_or(ScrapeError { reason: "song artist is not found".to_string() })
}

fn extract_count(html: Html) -> ScrapeResult<String> {
    let selector = Selector::parse(".heroMeta > .count").unwrap();
    html.select(&selector)
        .next()
        .and_then(|a| a.text().next())
        .map(|text| text.trim().to_string())
        .ok_or(ScrapeError { reason: "count is not found".to_string() })
}

fn extract_track(node: ElementRef) -> Track {
    let img_selector    = Selector::parse(".artworkImage > img").unwrap();
    let title_selector  = Selector::parse(".title").unwrap();
    let artist_selector = Selector::parse(".artist").unwrap();
    let audio_selector  = Selector::parse(".playlist-audio").unwrap();

    let mut title:       String = "".to_string();
    let mut artwork_url: String = "".to_string();
    let mut artist:      String = "".to_string();
    let mut audio_url:   String = "".to_string();
    if let Some(n) = node.select(&title_selector).next() {
        if let Some(text) = n.text().next() {
            title = text.trim().to_string();
        }
    }

    if let Some(n) = node.select(&artist_selector).next() {
        if let Some(text) = n.text().next() {
            artist = text.trim().to_string();
        }
    }

    if let Some(n) = node.select(&img_selector).next() {
        if let Some(url) = n.value().attr("src") {
            artwork_url = url.trim().to_string();
        }
    }

    if let Some(n) = node.select(&audio_selector).next() {
        if let Some(url) = n.value().attr("data-url") {
            audio_url = url.trim().to_string();
        }
    }

    Track {
        title:       title,
        artwork_url: artwork_url,
        artist:      artist,
        audio_url:   audio_url,
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_fetch_playlist() {
        let playlist = fetch_playlist("pl.2ff0e502db0c44a598a7cb2261a5e6b2", "jp").unwrap();
        assert_eq!(playlist.id, "pl.2ff0e502db0c44a598a7cb2261a5e6b2");
    }
    #[test]
    fn test_fetch_album() {
        let album = fetch_album("1160715126", "jp").unwrap();
        assert_eq!(album.id, "1160715126");
    }
    #[test]
    fn test_fetch_song() {
        let song = fetch_song("1160715431", "jp").unwrap();
        assert_eq!(song.id, "1160715431");
    }
    #[test]
    fn test_parse_url_as_album() {
        let album_song_link = "https://geo.itunes.apple.com/us/album/last-nite/id266376953?i=266377010&mt=1&app=music";
        match parse_url_as_album(album_song_link) {
            Some((country, name, id, song_id)) => {
                assert_eq!(&country         , "us");
                assert_eq!(&name            , "last-nite");
                assert_eq!(&id              , "266376953");
                assert_eq!(&song_id.unwrap(), "266377010");
            },
            None => assert!(false),
        }
    }
    #[test]
    fn test_parse_url_as_playlist() {
        let playlist_link = "https://itunes.apple.com/us/playlist/the-strokes-essentials/idpl.3a7a911b00c048ebba63b651935a241a?mt=1&app=music";
        match parse_url_as_playlist(playlist_link) {
            Some((country, name, id, song_id)) => {
                assert_eq!(&country         , "us");
                assert_eq!(&name            , "the-strokes-essentials");
                assert_eq!(&id              , "3a7a911b00c048ebba63b651935a241a");
                assert!(song_id.is_none());
            },
            None => assert!(false),
        }
    }
}
<|endoftext|>


<commit_before>extern crate rs6502;

use std::io::{self, BufRead, Write};
use std::sync::mpsc::{channel, Receiver};
use std::thread;

use rs6502::{Cpu, Disassembler};

#[derive(Debug)]
pub struct MemoryMonitor {
    pub enabled: bool,
    start_addr: usize,
    end_addr: usize,
}

pub struct VirtualMachine {
    pub cpu: Cpu,
    pub monitor: MemoryMonitor,
    code_offset: u16,
    clock_rate: Option<u32>,
    receiver: Receiver<String>,
    last_command: String,
    breakpoints: [u8; 64 * 1024],
    broken: bool,
    step: bool,
}

impl VirtualMachine {
    pub fn new<CR>(cpu: Cpu, code_offset: u16, clock_rate: CR) -> VirtualMachine
        where CR: Into<Option<u32>>
    {
        let (tx, rx) = channel();

        thread::spawn(move || {
            std::io::stdout().write(b"hakka> ").unwrap();
            std::io::stdout().flush().unwrap();

            loop {
                let mut line = String::new();
                let stdin = io::stdin();
                stdin.lock().read_line(&mut line).expect("Could not read line");
                tx.send(line).unwrap();
            }
        });

        VirtualMachine {
            cpu: cpu,
            code_offset: code_offset,
            clock_rate: clock_rate.into(),
            receiver: rx,
            monitor: MemoryMonitor {
                enabled: false,
                start_addr: 0,
                end_addr: 0,
            },
            last_command: "".into(),
            breakpoints: [0; 64 * 1024],
            broken: false,
            step: false,
        }
    }

    pub fn cycle(&mut self) {
        if let Some(clock_rate) = self.clock_rate {
            let mut n = 0;
            while (n < clock_rate && !self.broken) || self.step {
                n += self.cpu.step().expect("SEGFAULT") as u32;
                if self.breakpoints[self.cpu.registers.PC as usize] > 0 {
                    self.broken = true;
                    println!("");
                    println!("BREAKPOINT hit at {:04X}", self.cpu.registers.PC);
                }
                if self.step {
                    self.dump_local_disassembly();
                }
                self.step = false;
            }
        } else {
            self.cpu.step().expect("SEGFAULT");
            if self.step {
                self.dump_local_disassembly();
            }
            self.step = false;
            if self.breakpoints[self.cpu.registers.PC as usize] > 0 {
                self.broken = true;
                println!("");
                println!("BREAKPOINT hit at {:04X}", self.cpu.registers.PC);
            }
        }
    }

    pub fn try_execute_command(&mut self) {
        if let Ok(input) = self.receiver.try_recv() {
            let mut input: String = input.trim().into();

            if input.len() == 0 {
                if self.monitor.enabled {
                    self.monitor.enabled = false;
                } else {
                    input = self.last_command.clone();
                }
            }

            if input == "source" {
                self.dump_disassembly();
            }

            if input == "list" {
                self.dump_local_disassembly();
            }

            if input.starts_with("monitor") {
                if self.monitor.enabled {
                    self.monitor.enabled = false;
                } else {
                    self.enable_memory_monitor(&input);
                }
            }

            if input.starts_with("memset") {
                let parts: Vec<&str> = input.split(" ").collect();
                if parts.len() < 3 {
                    println!("ERR: Requires 2 arguments. Example: memset 0x00 0x01 to store 0x01 \
                              in 0x00.");
                } else if parts.len() == 3 {
                    if let Ok(dst) = u16::from_str_radix(&parts[1].replace("0x", "")[..], 16) {
                        if let Ok(src) = u8::from_str_radix(&parts[2].replace("0x", "")[..], 16) {
                            self.cpu.memory[dst as usize] = src;
                        } else {
                            println!("ERR: Unable to parse source byte value");
                        }
                    } else {
                        println!("ERR: Unable to parse destination byte value");
                    }
                } else {
                    if let Ok(mut dst) = usize::from_str_radix(&parts[1].replace("0x", "")[..],
                                                               16) {
                        for p in &parts[2..] {
                            if let Ok(byte) = u8::from_str_radix(&p.replace("0x", "")[..], 16) {
                                self.cpu.memory[dst] = byte;
                                dst += 0x01;
                            } else {
                                println!("ERR: Unable to parse source byte value");
                            }
                        }
                    } else {
                        println!("ERR: Unable to parse destination byte value");
                    }
                }
            }

            if input.starts_with("memdmp") {
                // 1 argument assumes 1 memory "page"
                let parts: Vec<&str> = input.split(" ").collect();
                if parts.len() == 2 {
                    if let Ok(page_number) = parts[1].parse() {

                        self.dump_memory_page(page_number);
                    } else {
                        println!("ERR: Unable to parse memory page");
                    }
                } else if parts.len() == 3 {
                    // A memory range instead
                    if let Ok(start) = u16::from_str_radix(&parts[1].replace("0x", "")[..], 16) {
                        if let Ok(end) = u16::from_str_radix(&parts[2].replace("0x", "")[..], 16) {
                            self.dump_memory_range(start, end);
                        } else {
                            println!("ERR: Unable to parse end address value");
                        }
                    } else {
                        println!("ERR: Unable to parse start address value");
                    }
                }
            }

            if input == "registers" {
                self.dump_registers();
            }

            if input.starts_with("break") {
                // 1 argument assumes 1 memory "page"
                let parts: Vec<&str> = input.split(" ").collect();
                if parts.len() == 2 {
                    if let Ok(addr) = usize::from_str_radix(&parts[1][..], 16) {
                        if self.breakpoints[addr] > 0 {
                            self.breakpoints[addr] = 0;
                        } else {
                            self.breakpoints[addr] = 1;
                        }
                    } else {
                        println!("Err: Unable to parse breakpoint address");
                    }
                } else {
                    println!("ERR: Requires 1 argument");
                }
            }

            if input == "continue" {
                self.broken = false;
                println!("Execution resumed");
            }

            if input == "step" {
                self.step = true;
            }

            std::io::stdout().write(b"hakka> ").unwrap();
            std::io::stdout().flush().unwrap();

            // Don't assign a blank command as a last command
            if input.len() > 0 {
                self.last_command = input.clone();
            }
        }

    }

    fn enable_memory_monitor(&mut self, input: &str) {
        let parts: Vec<&str> = input.split(" ").collect();
        if parts.len() < 3 {
            println!("ERR: Requires 2 arguments. Example: monitor 0x00 0xFF");
        } else {
            let start = usize::from_str_radix(&parts[1].replace("0x", "")[..], 16).unwrap();
            let end = usize::from_str_radix(&parts[2].replace("0x", "")[..], 16).unwrap();

            self.monitor.start_addr = start;
            self.monitor.end_addr = end;
            self.monitor.enabled = true;
        }
    }

    fn dump_disassembly(&self) {
        println!("");
        println!("-- Disassembly --");

        let disassembler = Disassembler::with_offset(self.code_offset);
        let pairs = disassembler.disassemble_with_addresses(self.cpu.get_code());
        let result = self.highlight_lines(self.cpu.registers.PC as usize, pairs, false).join("");
        print!("{}", result);
    }

    fn dump_local_disassembly(&self) {
        println!("");
        println!("-- Disassembly --");

        let disassembler = Disassembler::with_offset(self.code_offset);
        let pc = self.cpu.registers.PC as usize;
        let code = self.cpu.get_code();
        let pairs = disassembler.disassemble_with_addresses(code);
        let result = self.highlight_lines(pc, pairs, true).join("");
        print!("{}", result);
    }

    pub fn dump_memory_page(&self, page: usize) {
        let mut addr = page * 0x100;
        println!("-- Memory dump --");
        for chunk in self.cpu.memory[page * 0x100..(page * 0x100) + 0x100].chunks(8) {
            print!("{:04X}: ", addr);
            for b in chunk {
                print!("{:02X} ", *b);
            }
            addr += 0x08;
            println!("");
        }
    }

    pub fn dump_memory(&self) {
        println!("-- Memory dump --");
        for chunk in self.cpu.memory[self.monitor.start_addr..self.monitor.end_addr + 0x01]
            .chunks(8) {
            for b in chunk {
                print!("{:02X} ", *b);
            }
            println!("");
        }
    }

    fn dump_memory_range(&self, start: u16, end: u16) {
        println!("-- Memory dump --");
        let start = start as usize;
        let end = end as usize;
        for chunk in self.cpu.memory[start..end + 0x01].chunks(8) {
            for b in chunk {
                print!("{:02X} ", *b);
            }
            println!("");
        }
    }

    fn dump_registers(&self) {
        println!("-- Registers --");
        println!("A: {} ({:04X})", self.cpu.registers.A, self.cpu.registers.A);
        println!("X: {} ({:04X})", self.cpu.registers.X, self.cpu.registers.X);
        println!("Y: {} ({:04X})", self.cpu.registers.Y, self.cpu.registers.Y);
        println!("PC: {} ({:04X})",
                 self.cpu.registers.PC,
                 self.cpu.registers.PC);
        println!("S: {} ({:04X})",
                 self.cpu.stack.pointer,
                 self.cpu.stack.pointer);
    }

    fn highlight_lines(&self,
                       pc: usize,
                       pairs: Vec<(String, u16)>,
                       limit_results: bool)
                       -> Vec<String> {
        let mut result = Vec::new();

        let base = pc - self.code_offset as usize;

        for pair in pairs {
            if limit_results {
                let start = if base > 0x0A { base - 0x0A } else { 0 };
                if (pair.1 as usize) < start || (pair.1 as usize) > base + 0x0A {
                    continue;
                }
            }
            let current_line = pc as u16 == self.code_offset + pair.1;
            let breakpoint = self.breakpoints[self.code_offset as usize + pair.1 as usize] > 0x00;

            if breakpoint && current_line {
                result.push(format!("> * {}", pair.0));
            } else if breakpoint && !current_line {
                result.push(format!("  * {}", pair.0));
            } else if !breakpoint && current_line {
                result.push(format!(">   {}", pair.0));
            } else {
                result.push(format!("    {}", pair.0));
            }
        }

        result
    }
}<commit_msg>Add helptext and command shortcuts<commit_after>extern crate rs6502;

use std::io::{self, BufRead, Write};
use std::sync::mpsc::{channel, Receiver};
use std::thread;

use rs6502::{Cpu, Disassembler};

const HELPTEXT: &'static str = "

HAKKA
-----

Commands:

break | b: addr
       - Toggles a breakpoint at a specific address. If the
         program counter hits this address, execution
         stops.

step | s
       - Executes the current instruction before breaking
         execution again.

continue | c
       - Resumes execution of code within the virtual machine.

list
       - Lists the code surrounding the current program
         counter.

memset | set: addr args [args, ...]
       - memset sets the value of memory directly.

memdmp | dmp: page || start end
       - memdmp dumps a single memory page, or a specified
         memory range from <start> to <end> (inclusive).

monitor | mon: start end
       - monitor dumps the memory between start and end (inclusive)
         every second. Press ENTER to stop the monitor.

source
       - Lists the code currently running in the virtual
         machine. A '>' symbol indicates the current
         program counter.

registers | reg
       - Lists the CPU registers and their current values.

help
       - Lists this help text.
";

#[derive(Debug)]
pub struct MemoryMonitor {
    pub enabled: bool,
    start_addr: usize,
    end_addr: usize,
}

pub struct VirtualMachine {
    pub cpu: Cpu,
    pub monitor: MemoryMonitor,
    code_offset: u16,
    clock_rate: Option<u32>,
    receiver: Receiver<String>,
    last_command: String,
    breakpoints: [u8; 64 * 1024],
    broken: bool,
    step: bool,
}

impl VirtualMachine {
    pub fn new<CR>(cpu: Cpu, code_offset: u16, clock_rate: CR) -> VirtualMachine
        where CR: Into<Option<u32>>
    {
        let (tx, rx) = channel();

        thread::spawn(move || {
            println!("Welcome to hakka. Type 'help' to get started.");
            println!("");

            std::io::stdout().write(b"hakka> ").unwrap();
            std::io::stdout().flush().unwrap();

            loop {
                let mut line = String::new();
                let stdin = io::stdin();
                let mut lock = stdin.lock();
                if let Ok(_) = lock.read_line(&mut line) {
                    tx.send(line).unwrap();
                } else {
                    break;
                }
            }
        });

        VirtualMachine {
            cpu: cpu,
            code_offset: code_offset,
            clock_rate: clock_rate.into(),
            receiver: rx,
            monitor: MemoryMonitor {
                enabled: false,
                start_addr: 0,
                end_addr: 0,
            },
            last_command: "".into(),
            breakpoints: [0; 64 * 1024],
            broken: false,
            step: false,
        }
    }

    /// Cycles the Virtual Machine CPU according to the clock rate
    pub fn cycle(&mut self) {
        if let Some(clock_rate) = self.clock_rate {
            let mut n = 0;
            while (n < clock_rate && !self.broken) || self.step {
                n += self.cpu.step().expect("SEGFAULT") as u32;
                if self.breakpoints[self.cpu.registers.PC as usize] > 0 {
                    self.broken = true;
                    println!("");
                    println!("BREAKPOINT hit at {:04X}", self.cpu.registers.PC);
                }
                // If we stepped, dump the local disassembly
                if self.step {
                    self.dump_local_disassembly();
                }
                self.step = false;
            }
        } else {
            self.cpu.step().expect("SEGFAULT");
            if self.step {
                self.dump_local_disassembly();
            }
            self.step = false;
            if self.breakpoints[self.cpu.registers.PC as usize] > 0 {
                self.broken = true;
                println!("");
                println!("BREAKPOINT hit at {:04X}", self.cpu.registers.PC);
            }
        }
    }

    pub fn try_execute_command(&mut self) {
        if let Ok(input) = self.receiver.try_recv() {
            let mut input: String = input.trim().into();

            if input.len() == 0 {
                if self.monitor.enabled {
                    self.monitor.enabled = false;
                }
            }

            if input == "r" || input == "repeat" {
                input = self.last_command.clone();
            }

            let parts = input.split(" ").collect::<Vec<_>>();

            if parts[0] == "help" {
                print!("{}", HELPTEXT);
            }

            if parts[0] == "source" {
                self.dump_disassembly();
            }

            if parts[0] == "list" {
                self.dump_local_disassembly();
            }

            if parts[0] == "monitor" || parts[0] == "mon" {
                if self.monitor.enabled {
                    self.monitor.enabled = false;
                } else {
                    self.enable_memory_monitor(&input);
                }
            }

            if parts[0] == "memset" || parts[0] == "set" {
                if parts.len() < 3 {
                    println!("ERR: Requires 2 arguments. Example: memset 0x00 0x01 to store 0x01 \
                              in 0x00.");
                } else if parts.len() == 3 {
                    if let Ok(dst) = u16::from_str_radix(&parts[1].replace("0x", "")[..], 16) {
                        if let Ok(src) = u8::from_str_radix(&parts[2].replace("0x", "")[..], 16) {
                            self.cpu.memory[dst as usize] = src;
                        } else {
                            println!("ERR: Unable to parse source byte value");
                        }
                    } else {
                        println!("ERR: Unable to parse destination byte value");
                    }
                } else {
                    if let Ok(mut dst) = usize::from_str_radix(&parts[1].replace("0x", "")[..],
                                                               16) {
                        for p in &parts[2..] {
                            if let Ok(byte) = u8::from_str_radix(&p.replace("0x", "")[..], 16) {
                                self.cpu.memory[dst] = byte;
                                dst += 0x01;
                            } else {
                                println!("ERR: Unable to parse source byte value");
                            }
                        }
                    } else {
                        println!("ERR: Unable to parse destination byte value");
                    }
                }
            }

            if parts[0] == "memdmp" || parts[0] == "dmp" {
                // 1 argument assumes 1 memory "page"
                if parts.len() == 2 {
                    if let Ok(page_number) = parts[1].parse() {

                        self.dump_memory_page(page_number);
                    } else {
                        println!("ERR: Unable to parse memory page");
                    }
                } else if parts.len() == 3 {
                    // A memory range instead
                    if let Ok(start) = u16::from_str_radix(&parts[1].replace("0x", "")[..], 16) {
                        if let Ok(end) = u16::from_str_radix(&parts[2].replace("0x", "")[..], 16) {
                            self.dump_memory_range(start, end);
                        } else {
                            println!("ERR: Unable to parse end address value");
                        }
                    } else {
                        println!("ERR: Unable to parse start address value");
                    }
                }
            }

            if parts[0] == "registers" || parts[0] == "reg" {
                self.dump_registers();
            }

            if parts[0] == "break" || parts[0] == "b" {
                // 1 argument assumes 1 memory "page"
                if parts.len() == 2 {
                    if let Ok(addr) = usize::from_str_radix(&parts[1][..], 16) {
                        if addr <= u16::max_value() as usize {
                            if self.breakpoints[addr] > 0 {
                                self.breakpoints[addr] = 0;
                            } else {
                                self.breakpoints[addr] = 1;
                            }
                        } else {
                            println!("ERR: Value outside addressable range.");
                        }
                    } else {
                        println!("ERR: Unable to parse breakpoint address");
                    }
                } else {
                    println!("ERR: Requires 1 argument");
                }
            }

            if parts[0] == "continue" || parts[0] == "c" {
                self.broken = false;
                println!("Execution resumed");
            }

            if parts[0] == "step" || parts[0] == "s" {
                self.step = true;
            }

            std::io::stdout().write(b"hakka> ").unwrap();
            std::io::stdout().flush().unwrap();

            // Don't assign a blank command as a last command
            if input.len() > 0 {
                self.last_command = input.clone();
            }
        }

    }

    fn enable_memory_monitor(&mut self, input: &str) {
        let parts: Vec<&str> = input.split(" ").collect();
        if parts.len() < 3 {
            println!("ERR: Requires 2 arguments. Example: monitor 0x00 0xFF");
        } else {
            let start = usize::from_str_radix(&parts[1].replace("0x", "")[..], 16).unwrap();
            let end = usize::from_str_radix(&parts[2].replace("0x", "")[..], 16).unwrap();

            self.monitor.start_addr = start;
            self.monitor.end_addr = end;
            self.monitor.enabled = true;
        }
    }

    fn dump_disassembly(&self) {
        println!("");
        println!("-- Disassembly --");

        let disassembler = Disassembler::with_offset(self.code_offset);
        let pairs = disassembler.disassemble_with_addresses(self.cpu.get_code());
        let result = self.highlight_lines(self.cpu.registers.PC as usize, pairs, false).join("");
        print!("{}", result);
    }

    fn dump_local_disassembly(&self) {
        println!("");
        println!("-- Disassembly --");

        let disassembler = Disassembler::with_offset(self.code_offset);
        let pc = self.cpu.registers.PC as usize;
        let code = self.cpu.get_code();
        let pairs = disassembler.disassemble_with_addresses(code);
        let result = self.highlight_lines(pc, pairs, true).join("");
        print!("{}", result);
    }

    pub fn dump_memory_page(&self, page: usize) {
        let mut addr = page * 0x100;
        println!("-- Memory dump --");
        for chunk in self.cpu.memory[page * 0x100..(page * 0x100) + 0x100].chunks(8) {
            print!("{:04X}: ", addr);
            for b in chunk {
                print!("{:02X} ", *b);
            }
            addr += 0x08;
            println!("");
        }
    }

    pub fn dump_memory(&self) {
        println!("-- Memory dump --");
        for chunk in self.cpu.memory[self.monitor.start_addr..self.monitor.end_addr + 0x01]
            .chunks(8) {
            for b in chunk {
                print!("{:02X} ", *b);
            }
            println!("");
        }
    }

    fn dump_memory_range(&self, start: u16, end: u16) {
        println!("-- Memory dump --");
        let start = start as usize;
        let end = end as usize;
        for chunk in self.cpu.memory[start..end + 0x01].chunks(8) {
            for b in chunk {
                print!("{:02X} ", *b);
            }
            println!("");
        }
    }

    fn dump_registers(&self) {
        println!("-- Registers --");
        println!("A: {} ({:04X})", self.cpu.registers.A, self.cpu.registers.A);
        println!("X: {} ({:04X})", self.cpu.registers.X, self.cpu.registers.X);
        println!("Y: {} ({:04X})", self.cpu.registers.Y, self.cpu.registers.Y);
        println!("PC: {} ({:04X})",
                 self.cpu.registers.PC,
                 self.cpu.registers.PC);
        println!("S: {} ({:04X})",
                 self.cpu.stack.pointer,
                 self.cpu.stack.pointer);
    }

    fn highlight_lines(&self,
                       pc: usize,
                       pairs: Vec<(String, u16)>,
                       limit_results: bool)
                       -> Vec<String> {
        let mut result = Vec::new();

        let base = pc - self.code_offset as usize;

        for pair in pairs {
            if limit_results {
                let start = if base > 0x0A { base - 0x0A } else { 0 };
                if (pair.1 as usize) < start || (pair.1 as usize) > base + 0x0A {
                    continue;
                }
            }
            let current_line = pc as u16 == self.code_offset + pair.1;
            let breakpoint = self.breakpoints[self.code_offset as usize + pair.1 as usize] > 0x00;

            if breakpoint && current_line {
                result.push(format!("> * {}", pair.0));
            } else if breakpoint && !current_line {
                result.push(format!("  * {}", pair.0));
            } else if !breakpoint && current_line {
                result.push(format!(">   {}", pair.0));
            } else {
                result.push(format!("    {}", pair.0));
            }
        }

        result
    }
}<|endoftext|>