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

//! Firmware protocol payload.

use crate::io::Read;
use crate::io::Write;
use crate::protocol::wire::FromWireError;
use crate::protocol::wire::FromWire;
use crate::protocol::wire::ToWireError;
use crate::protocol::wire::ToWire;
use crate::protocol::wire::WireEnum;

wire_enum! {
    /// The content type.
    pub enum ContentType: u8 {
        /// Unknown message type.
        Unknown = 0xff,

        /// Request to prepare for an update
        UpdatePrepareRequest = 0x01,

        /// Response to PrepareRequest
        UpdatePrepareResponse = 0x02,

        /// Request to rrite a chunk of firmware
        WriteChunkRequest = 0x03,

        /// Response to WriteChunkRequest
        WriteChunkResponse = 0x04,
    }
}

/// A parsed header.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Header {
    /// The content type following the header.
    pub content: ContentType,
}

/// The length of a firmware header on the wire, in bytes.
pub const HEADER_LEN: usize = 1;

impl<'a> FromWire<'a> for Header {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let content_u8 = r.read_be::<u8>()?;
        let content = ContentType::from_wire_value(content_u8).ok_or(FromWireError::OutOfRange)?;
        Ok(Self {
            content,
        })
    }
}

impl ToWire for Header {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        w.write_be(self.content.to_wire_value())?;
        Ok(())
    }
}

// ----------------------------------------------------------------------------

/// A message.
///
/// A message is identified by a [`ContentType`]:
///
/// This trait is not implemented by any of the message types
///
/// [`ContentType`]: enum.ContentType.html
pub trait Message<'req>: FromWire<'req> + ToWire {
    /// The unique [`ContentType`] for this `Message`.
    ///
    /// [`ContentType`]: enum.ContentType.html
    const TYPE: ContentType;
}

// ----------------------------------------------------------------------------

wire_enum! {
    /// Identifier for a segment and location.
    pub enum SegmentAndLocation: u8 {
        /// Unknown message type.
        Unknown = 0xff,

        /// RO in location A
        RoA = 0x01,

        /// RO in location B
        RoB = 0x02,

        /// RW in location A
        RwA = 0x03,

        /// RW in location B
        RwB = 0x04,
    }
}

// ----------------------------------------------------------------------------

/// A parsed update prepare request.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct UpdatePrepareRequest {
    /// The segment and location.
    pub segment_and_location: SegmentAndLocation,
}

/// The length of a update prepare request on the wire, in bytes.
pub const UPDATE_PREPARE_REQUEST_LEN: usize = 1;

impl Message<'_> for UpdatePrepareRequest {
    const TYPE: ContentType = ContentType::UpdatePrepareRequest;
}

impl<'a> FromWire<'a> for UpdatePrepareRequest {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let sal_u8 = r.read_be::<u8>()?;
        let segment_and_location = SegmentAndLocation::from_wire_value(sal_u8).ok_or(FromWireError::OutOfRange)?;
        Ok(Self {
            segment_and_location,
        })
    }
}

impl ToWire for UpdatePrepareRequest {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        w.write_be(self.segment_and_location.to_wire_value())?;
        Ok(())
    }
}

// ----------------------------------------------------------------------------

wire_enum! {
    /// The result of an update prepare request.
    pub enum UpdatePrepareResult: u8 {
        /// Unknown result type.
        Unknown = 0xff,

        /// Success
        Success = 0x00,

        /// Unspecified error
        Error = 0x01,

        /// Invalid segment and/or location
        InvalidSegmentAndLocation = 0x02,
    }
}

/// A parsed update prepare response.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct UpdatePrepareResponse {
    /// The segment and location.
    pub segment_and_location: SegmentAndLocation,

    /// The maximum chunk length per write.
    pub max_chunk_length: u16,

    /// The result of the update prepare request.
    pub result: UpdatePrepareResult,
}

/// The length of a update prepare response on the wire, in bytes.
pub const UPDATE_PREPARE_RESPONSE_LEN: usize = 4;

impl Message<'_> for UpdatePrepareResponse {
    const TYPE: ContentType = ContentType::UpdatePrepareResponse;
}

impl<'a> FromWire<'a> for UpdatePrepareResponse {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let sal_u8 = r.read_be::<u8>()?;
        let segment_and_location = SegmentAndLocation::from_wire_value(sal_u8).ok_or(FromWireError::OutOfRange)?;
        let max_chunk_length = r.read_be::<u16>()?;
        let result_u8 = r.read_be::<u8>()?;
        let result = UpdatePrepareResult::from_wire_value(result_u8).ok_or(FromWireError::OutOfRange)?;
        Ok(Self {
            segment_and_location,
            max_chunk_length,
            result,
        })
    }
}

impl ToWire for UpdatePrepareResponse {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        w.write_be(self.segment_and_location.to_wire_value())?;
        w.write_be(self.max_chunk_length)?;
        w.write_be(self.result.to_wire_value())?;
        Ok(())
    }
}

// ----------------------------------------------------------------------------

/// A parsed write chunk request.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct WriteChunkRequest<'a> {
    /// The segment and location.
    pub segment_and_location: SegmentAndLocation,

    /// The offset within the segment.
    pub offset: u32,

    /// The data to write
    pub data: &'a [u8],
}

/// The length of a write chunk request on the wire, in bytes.
pub const WRITE_CHUNK_REQUEST_LEN: usize = 5;

impl<'a> Message<'a> for WriteChunkRequest<'a> {
    const TYPE: ContentType = ContentType::WriteChunkRequest;
}

impl<'a> FromWire<'a> for WriteChunkRequest<'a> {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let sal_u8 = r.read_be::<u8>()?;
        let segment_and_location = SegmentAndLocation::from_wire_value(sal_u8).ok_or(FromWireError::OutOfRange)?;
        let offset = r.read_be::<u32>()?;
        let data_len = r.remaining_data();
        let data = r.read_bytes(data_len)?;
        Ok(Self {
            segment_and_location,
            offset,
            data,
        })
    }
}

impl ToWire for WriteChunkRequest<'_> {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        w.write_be(self.segment_and_location.to_wire_value())?;
        w.write_be(self.offset)?;
        w.write_bytes(self.data)?;
        Ok(())
    }
}

// ----------------------------------------------------------------------------

wire_enum! {
    /// The result of a write chunk request.
    pub enum WriteChunkResult: u8 {
        /// Unknown result type.
        Unknown = 0xff,

        /// Success
        Success = 0x00,

        /// Unspecified error
        Error = 0x01,

        /// Invalid segment and/or location
        InvalidSegmentAndLocation = 0x02,

        /// Invalid offset
        InvalidOffset = 0x03,

        /// Too much data
        DataTooLong = 0x04,

        /// Post-write compare failed
        CompareFailed = 0x05,
    }
}

/// A parsed write chunk response.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct WriteChunkResponse {
    /// The segment and location.
    pub segment_and_location: SegmentAndLocation,

    /// The offset within the segment,
    pub offset: u32,

    /// The result of the write chunk request.
    pub result: WriteChunkResult,
}

/// The length of an write chunk response on the wire, in bytes.
pub const WRITE_CHUNK_RESPONSE_LEN: usize = 6;

impl Message<'_> for WriteChunkResponse {
    const TYPE: ContentType = ContentType::WriteChunkResponse;
}

impl<'a> FromWire<'a> for WriteChunkResponse {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let sal_u8 = r.read_be::<u8>()?;
        let segment_and_location = SegmentAndLocation::from_wire_value(sal_u8).ok_or(FromWireError::OutOfRange)?;
        let offset = r.read_be::<u32>()?;
        let result_u8 = r.read_be::<u8>()?;
        let result = WriteChunkResult::from_wire_value(result_u8).ok_or(FromWireError::OutOfRange)?;
        Ok(Self {
            segment_and_location,
            offset,
            result,
        })
    }
}

impl ToWire for WriteChunkResponse {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        w.write_be(self.segment_and_location.to_wire_value())?;
        w.write_be(self.offset)?;
        w.write_be(self.result.to_wire_value())?;
        Ok(())
    }
}
<commit_msg>spiutils: Add InactiveSegmentsInfo and RebootRequest/Response messages. (#250)<commit_after>// Copyright 2021 lowRISC contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

//! Firmware protocol payload.

use crate::compat::firmware::BuildInfo;
use crate::compat::firmware::BUILD_INFO_LEN;
use crate::driver::firmware::SegmentInfo;
use crate::driver::firmware::SEGMENT_INFO_LEN;
use crate::io::Read;
use crate::io::Write;
use crate::protocol::wire::FromWireError;
use crate::protocol::wire::FromWire;
use crate::protocol::wire::ToWireError;
use crate::protocol::wire::ToWire;
use crate::protocol::wire::WireEnum;

wire_enum! {
    /// The content type.
    pub enum ContentType: u8 {
        /// Unknown message type.
        Unknown = 0xff,

        /// Request to prepare for an update
        UpdatePrepareRequest = 0x01,

        /// Response to PrepareRequest
        UpdatePrepareResponse = 0x02,

        /// Request to write a chunk of firmware
        WriteChunkRequest = 0x03,

        /// Response to WriteChunkRequest
        WriteChunkResponse = 0x04,

        /// Request information on inactive segments
        InactiveSegmentsInfoRequest = 0x05,

        /// Response to InactiveSegmentsInfoRequest
        InactiveSegmentsInfoResponse = 0x06,

        /// Request to reboot
        RebootRequest = 0x07,

        /// Response to RebootRequest
        RebootResponse = 0x08,
    }
}

/// A parsed header.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Header {
    /// The content type following the header.
    pub content: ContentType,
}

/// The length of a firmware header on the wire, in bytes.
pub const HEADER_LEN: usize = 1;

impl<'a> FromWire<'a> for Header {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let content_u8 = r.read_be::<u8>()?;
        let content = ContentType::from_wire_value(content_u8).ok_or(FromWireError::OutOfRange)?;
        Ok(Self {
            content,
        })
    }
}

impl ToWire for Header {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        w.write_be(self.content.to_wire_value())?;
        Ok(())
    }
}

// ----------------------------------------------------------------------------

/// A message.
///
/// A message is identified by a [`ContentType`]:
///
/// This trait is not implemented by any of the message types
///
/// [`ContentType`]: enum.ContentType.html
pub trait Message<'req>: FromWire<'req> + ToWire {
    /// The unique [`ContentType`] for this `Message`.
    ///
    /// [`ContentType`]: enum.ContentType.html
    const TYPE: ContentType;
}

// ----------------------------------------------------------------------------

wire_enum! {
    /// Identifier for a segment and location.
    pub enum SegmentAndLocation: u8 {
        /// Unknown message type.
        Unknown = 0xff,

        /// RO in location A
        RoA = 0x01,

        /// RO in location B
        RoB = 0x02,

        /// RW in location A
        RwA = 0x03,

        /// RW in location B
        RwB = 0x04,
    }
}

// ----------------------------------------------------------------------------

/// A parsed inactive segments info request.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct InactiveSegmentsInfoRequest {
}

/// The length of an inactive segments info request on the wire, in bytes.
pub const INACTIVE_SEGMENTS_INFO_REQUEST_LEN: usize = 0;

impl Message<'_> for InactiveSegmentsInfoRequest {
    const TYPE: ContentType = ContentType::InactiveSegmentsInfoRequest;
}

impl<'a> FromWire<'a> for InactiveSegmentsInfoRequest {
    fn from_wire<R: Read<'a>>(mut _r: R) -> Result<Self, FromWireError> {
        Ok(Self {})
    }
}

impl ToWire for InactiveSegmentsInfoRequest {
    fn to_wire<W: Write>(&self, mut _w: W) -> Result<(), ToWireError> {
        Ok(())
    }
}

// ----------------------------------------------------------------------------

/// A parsed inactive segments info response.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct InactiveSegmentsInfoResponse {
    /// The inactive RO.
    pub ro: SegmentInfo,

    /// The inactive RW.
    pub rw: SegmentInfo,
}

/// The length of an inactive segments info response on the wire, in bytes.
pub const INACTIVE_SEGMENTS_INFO_RESPONSE_LEN: usize = 2 * SEGMENT_INFO_LEN;

impl Message<'_> for InactiveSegmentsInfoResponse {
    const TYPE: ContentType = ContentType::InactiveSegmentsInfoResponse;
}

impl<'a> FromWire<'a> for InactiveSegmentsInfoResponse {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let ro = SegmentInfo::from_wire(&mut r)?;
        let rw = SegmentInfo::from_wire(&mut r)?;
        Ok(Self {
            ro,
            rw,
        })
    }
}

impl ToWire for InactiveSegmentsInfoResponse {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        self.ro.to_wire(&mut w)?;
        self.rw.to_wire(&mut w)?;
        Ok(())
    }
}

// ----------------------------------------------------------------------------

/// A parsed firmware info message.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct FirmwareInfo {
    /// The segment and location.
    pub segment_and_location: SegmentAndLocation,

    /// The build information.
    pub build_info: BuildInfo,
}

/// The length of a firmware info struct on the wire, in bytes.
pub const FIRMWARE_INFO_LEN: usize = 1 + BUILD_INFO_LEN;

impl<'a> FromWire<'a> for FirmwareInfo {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let sal_u8 = r.read_be::<u8>()?;
        let segment_and_location = SegmentAndLocation::from_wire_value(sal_u8).ok_or(FromWireError::OutOfRange)?;
        let build_info = BuildInfo::from_wire(r)?;
        Ok(Self {
            segment_and_location,
            build_info,
        })
    }
}

impl ToWire for FirmwareInfo {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        w.write_be(self.segment_and_location.to_wire_value())?;
        self.build_info.to_wire(w)?;
        Ok(())
    }
}

// ----------------------------------------------------------------------------

/// A parsed update prepare request.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct UpdatePrepareRequest {
    /// The segment and location.
    pub segment_and_location: SegmentAndLocation,
}

/// The length of a update prepare request on the wire, in bytes.
pub const UPDATE_PREPARE_REQUEST_LEN: usize = 1;

impl Message<'_> for UpdatePrepareRequest {
    const TYPE: ContentType = ContentType::UpdatePrepareRequest;
}

impl<'a> FromWire<'a> for UpdatePrepareRequest {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let sal_u8 = r.read_be::<u8>()?;
        let segment_and_location = SegmentAndLocation::from_wire_value(sal_u8).ok_or(FromWireError::OutOfRange)?;
        Ok(Self {
            segment_and_location,
        })
    }
}

impl ToWire for UpdatePrepareRequest {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        w.write_be(self.segment_and_location.to_wire_value())?;
        Ok(())
    }
}

// ----------------------------------------------------------------------------

wire_enum! {
    /// The result of an update prepare request.
    pub enum UpdatePrepareResult: u8 {
        /// Unknown result type.
        Unknown = 0xff,

        /// Success
        Success = 0x00,

        /// Unspecified error
        Error = 0x01,

        /// Invalid segment and/or location
        InvalidSegmentAndLocation = 0x02,
    }
}

/// A parsed update prepare response.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct UpdatePrepareResponse {
    /// The segment and location.
    pub segment_and_location: SegmentAndLocation,

    /// The maximum chunk length per write.
    pub max_chunk_length: u16,

    /// The result of the update prepare request.
    pub result: UpdatePrepareResult,
}

/// The length of a update prepare response on the wire, in bytes.
pub const UPDATE_PREPARE_RESPONSE_LEN: usize = 4;

impl Message<'_> for UpdatePrepareResponse {
    const TYPE: ContentType = ContentType::UpdatePrepareResponse;
}

impl<'a> FromWire<'a> for UpdatePrepareResponse {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let sal_u8 = r.read_be::<u8>()?;
        let segment_and_location = SegmentAndLocation::from_wire_value(sal_u8).ok_or(FromWireError::OutOfRange)?;
        let max_chunk_length = r.read_be::<u16>()?;
        let result_u8 = r.read_be::<u8>()?;
        let result = UpdatePrepareResult::from_wire_value(result_u8).ok_or(FromWireError::OutOfRange)?;
        Ok(Self {
            segment_and_location,
            max_chunk_length,
            result,
        })
    }
}

impl ToWire for UpdatePrepareResponse {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        w.write_be(self.segment_and_location.to_wire_value())?;
        w.write_be(self.max_chunk_length)?;
        w.write_be(self.result.to_wire_value())?;
        Ok(())
    }
}

// ----------------------------------------------------------------------------

/// A parsed write chunk request.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct WriteChunkRequest<'a> {
    /// The segment and location.
    pub segment_and_location: SegmentAndLocation,

    /// The offset within the segment.
    pub offset: u32,

    /// The data to write
    pub data: &'a [u8],
}

/// The length of a write chunk request on the wire, in bytes.
pub const WRITE_CHUNK_REQUEST_LEN: usize = 5;

impl<'a> Message<'a> for WriteChunkRequest<'a> {
    const TYPE: ContentType = ContentType::WriteChunkRequest;
}

impl<'a> FromWire<'a> for WriteChunkRequest<'a> {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let sal_u8 = r.read_be::<u8>()?;
        let segment_and_location = SegmentAndLocation::from_wire_value(sal_u8).ok_or(FromWireError::OutOfRange)?;
        let offset = r.read_be::<u32>()?;
        let data_len = r.remaining_data();
        let data = r.read_bytes(data_len)?;
        Ok(Self {
            segment_and_location,
            offset,
            data,
        })
    }
}

impl ToWire for WriteChunkRequest<'_> {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        w.write_be(self.segment_and_location.to_wire_value())?;
        w.write_be(self.offset)?;
        w.write_bytes(self.data)?;
        Ok(())
    }
}

// ----------------------------------------------------------------------------

wire_enum! {
    /// The result of a write chunk request.
    pub enum WriteChunkResult: u8 {
        /// Unknown result type.
        Unknown = 0xff,

        /// Success
        Success = 0x00,

        /// Unspecified error
        Error = 0x01,

        /// Invalid segment and/or location
        InvalidSegmentAndLocation = 0x02,

        /// Invalid offset
        InvalidOffset = 0x03,

        /// Too much data
        DataTooLong = 0x04,

        /// Post-write compare failed
        CompareFailed = 0x05,
    }
}

/// A parsed write chunk response.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct WriteChunkResponse {
    /// The segment and location.
    pub segment_and_location: SegmentAndLocation,

    /// The offset within the segment,
    pub offset: u32,

    /// The result of the write chunk request.
    pub result: WriteChunkResult,
}

/// The length of an write chunk response on the wire, in bytes.
pub const WRITE_CHUNK_RESPONSE_LEN: usize = 6;

impl Message<'_> for WriteChunkResponse {
    const TYPE: ContentType = ContentType::WriteChunkResponse;
}

impl<'a> FromWire<'a> for WriteChunkResponse {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let sal_u8 = r.read_be::<u8>()?;
        let segment_and_location = SegmentAndLocation::from_wire_value(sal_u8).ok_or(FromWireError::OutOfRange)?;
        let offset = r.read_be::<u32>()?;
        let result_u8 = r.read_be::<u8>()?;
        let result = WriteChunkResult::from_wire_value(result_u8).ok_or(FromWireError::OutOfRange)?;
        Ok(Self {
            segment_and_location,
            offset,
            result,
        })
    }
}

impl ToWire for WriteChunkResponse {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        w.write_be(self.segment_and_location.to_wire_value())?;
        w.write_be(self.offset)?;
        w.write_be(self.result.to_wire_value())?;
        Ok(())
    }
}

// ----------------------------------------------------------------------------

wire_enum! {
    /// When to perform the reboot.
    pub enum RebootTime: u8 {
        /// Reboot immediately
        Immediate = 0x00,

        /// Reboot after a delay or when the BMC resets.
        Delayed = 0x01,
    }
}

/// A parsed reboot request.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct RebootRequest {
    /// When to reboot.
    pub time: RebootTime,
}

/// The length of a reboot request on the wire, in bytes.
pub const REBOOT_REQUEST_LEN: usize = 1;

impl Message<'_> for RebootRequest {
    const TYPE: ContentType = ContentType::RebootRequest;
}

impl<'a> FromWire<'a> for RebootRequest {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let time_u8 = r.read_be::<u8>()?;
        let time = RebootTime::from_wire_value(time_u8).ok_or(FromWireError::OutOfRange)?;
        Ok(Self {
            time,
        })
    }
}

impl ToWire for RebootRequest {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        w.write_be(self.time.to_wire_value())?;
        Ok(())
    }
}

// ----------------------------------------------------------------------------

wire_enum! {
    /// The result of a reboot request.
    pub enum RebootResult: u8 {
        /// Success
        Success = 0x00,

        /// Unspecified error
        Error = 0x01,
    }
}

/// A parsed reboot response.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct RebootResponse {
    /// When to reboot from the request.
    pub time: RebootTime,

    /// The result of the reboot request.
    pub result: RebootResult,
}

/// The length of a reboot response on the wire, in bytes.
pub const REBOOT_RESPONSE_LEN: usize = 2;

impl Message<'_> for RebootResponse {
    const TYPE: ContentType = ContentType::RebootResponse;
}

impl<'a> FromWire<'a> for RebootResponse {
    fn from_wire<R: Read<'a>>(mut r: R) -> Result<Self, FromWireError> {
        let time_u8 = r.read_be::<u8>()?;
        let time = RebootTime::from_wire_value(time_u8).ok_or(FromWireError::OutOfRange)?;
        let result_u8 = r.read_be::<u8>()?;
        let result = RebootResult::from_wire_value(result_u8).ok_or(FromWireError::OutOfRange)?;
        Ok(Self {
            time,
            result,
        })
    }
}

impl ToWire for RebootResponse {
    fn to_wire<W: Write>(&self, mut w: W) -> Result<(), ToWireError> {
        w.write_be(self.time.to_wire_value())?;
        w.write_be(self.result.to_wire_value())?;
        Ok(())
    }
}
<|endoftext|>


<commit_before>use std::path::{Path, PathBuf};
use std::ffi::OsStr;
use std::collections::HashSet;

use clap::{App, AppSettings, Arg, ArgMatches};
use hyper::method::Method;
use hyper::status::StatusCode;
use multipart::client::Multipart;
use mime::{Mime, TopLevel};
use mime_guess::guess_mime_type_opt;
use serde_json;
use walkdir::WalkDir;

use CliResult;
use commands::Config;
use utils::make_subcommand;

#[derive(Debug, Serialize)]
struct NewRelease {
    version: String,
    #[serde(rename="ref", skip_serializing_if="Option::is_none")]
    reference: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    url: Option<String>
}

#[derive(Debug, Deserialize)]
struct ReleaseInfo {
    version: String,
    #[serde(rename="ref")]
    reference: Option<String>,
    url: Option<String>,
    #[serde(rename="dateCreated")]
    date_created: String,
    #[serde(rename="dateReleased")]
    date_released: Option<String>,
    #[serde(rename="newGroups")]
    new_groups: u64,
}

#[derive(Debug, Deserialize)]
struct Artifact {
    id: String,
    sha1: String,
    name: String,
    size: u64,
}

fn get_content_type(mime: Mime) -> String {
    format!("{}{}", mime, match mime {
        Mime(TopLevel::Text, _, _) => {
            "; charset=utf-8"
        },
        _ => "",
    })
}

fn upload_file(config: &Config, org: &str, project: &str, version: &str,
               local_path: &Path, name: &str) -> CliResult<Option<Artifact>> {
    let req = config.prepare_api_request(Method::Post,
        &format!("/projects/{}/{}/releases/{}/files/", org, project, version))?;
    let mut mp = Multipart::from_request_sized(req)?;
    let mimetype = guess_mime_type_opt(local_path).or_else(|| {
        guess_mime_type_opt(&Path::new(name))
    }).or_else(|| {
        "application/octet-stream".parse().ok()
    }).unwrap();
    mp.write_file("file", &local_path)?;
    mp.write_text("header", &format!("Content-Type:{}", get_content_type(mimetype)))?;
    mp.write_text("name", name)?;
    let mut resp = mp.send()?;
    if resp.status == StatusCode::Conflict {
        Ok(None)
    } else if !resp.status.is_success() {
        fail!(resp);
    } else {
        Ok(Some(serde_json::from_reader(&mut resp)?))
    }
}

fn list_files(config: &Config, org: &str, project: &str, release: &str) -> CliResult<Vec<Artifact>> {
    let mut resp = config.api_request(
        Method::Get, &format!("/projects/{}/{}/releases/{}/files/", org, project, release))?;
    // XXX: handle pagination
    if !resp.status.is_success() {
        fail!(resp);
    }
    Ok(serde_json::from_reader(&mut resp)?)
}

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b>
{
    app
        .about("manage releases on Sentry")
        .setting(AppSettings::SubcommandRequiredElseHelp)
        .arg(Arg::with_name("org")
             .value_name("ORG")
             .long("org")
             .short("o")
             .help("The organization slug"))
        .arg(Arg::with_name("project")
             .value_name("PROJECT")
             .long("project")
             .short("p")
             .help("The project slug"))
        .subcommand(make_subcommand("new")
            .about("Create a new release")
            .arg(Arg::with_name("version")
                 .value_name("VERSION")
                 .required(true)
                 .index(1)
                 .help("The version identifier for this release"))
            .arg(Arg::with_name("ref")
                 .long("ref")
                 .value_name("REF")
                 .help("Optional commit reference (commit hash)"))
            .arg(Arg::with_name("url")
                 .long("url")
                 .value_name("URL")
                 .help("Optional URL to the release for information purposes")))
        .subcommand(make_subcommand("delete")
            .about("Delete a release")
            .arg(Arg::with_name("version")
                 .value_name("VERSION")
                 .required(true)
                 .index(1)
                 .help("The version to delete")))
        .subcommand(make_subcommand("list")
            .about("list the most recent releases"))
        .subcommand(make_subcommand("files")
            .about("manage release artifact files")
            .setting(AppSettings::SubcommandRequiredElseHelp)
            .arg(Arg::with_name("version")
                 .value_name("VERSION")
                 .required(true)
                 .index(1)
                 .help("The release to manage the files of"))
            .subcommand(make_subcommand("list")
                .about("List all release files"))
            .subcommand(make_subcommand("delete")
                .about("Delete a release file")
                .arg(Arg::with_name("all")
                     .short("A")
                     .long("all")
                     .help("deletes all files"))
                .arg(Arg::with_name("names")
                     .value_name("NAMES")
                     .index(1)
                     .multiple(true)
                     .help("a list of filenames to delete.")))
            .subcommand(make_subcommand("upload")
                .about("Uploads a file for a given release")
                .arg(Arg::with_name("path")
                     .value_name("PATH")
                     .index(1)
                     .required(true)
                     .help("The file to upload"))
                .arg(Arg::with_name("name")
                     .index(2)
                     .value_name("NAME")
                     .help("The name of the file on the server.")))
            .subcommand(make_subcommand("upload-sourcemaps")
                .about("Uploads sourcemap information for a given release")
                .arg(Arg::with_name("paths")
                     .value_name("PATHS")
                     .index(1)
                     .required(true)
                     .multiple(true)
                     .help("The files to upload"))
                .arg(Arg::with_name("url_prefix")
                     .short("u")
                     .long("url-prefix")
                     .required(true)
                     .value_name("PREFIX")
                     .help("The URL prefix to prepend to all filenames"))
                .arg(Arg::with_name("extensions")
                     .long("ext")
                     .short("x")
                     .multiple(true)
                     .help("Add a file extension to the list of files to upload."))))
}

pub fn execute_new<'a>(matches: &ArgMatches<'a>, config: &Config,
                       org: &str, project: &str) -> CliResult<()> {
    let release = NewRelease {
        version: matches.value_of("version").unwrap().to_owned(),
        reference: matches.value_of("ref").map(|x| x.to_owned()),
        url: matches.value_of("url").map(|x| x.to_owned()),
    };
    let mut resp = config.json_api_request(
        Method::Post, &format!("/projects/{}/{}/releases/", org, project),
        &release)?;
    if !resp.status.is_success() {
        fail!(resp);
    } else {
        let info_rv : ReleaseInfo = serde_json::from_reader(&mut resp)?;
        println!("Created release {}.", info_rv.version);
    }
    Ok(())
}

pub fn execute_delete<'a>(matches: &ArgMatches<'a>, config: &Config,
                          org: &str, project: &str) -> CliResult<()> {
    let version = matches.value_of("version").unwrap();
    let resp = config.api_request(
        Method::Delete, &format!("/projects/{}/{}/releases/{}/", org, project, version))?;
    if resp.status == StatusCode::NotFound {
        println!("Did nothing. Release with this version ({}) does not exist.", version);
    } else if !resp.status.is_success() {
        fail!(resp);
    } else {
        println!("Deleted release {}!", version);
    }
    Ok(())
}

pub fn execute_list<'a>(_matches: &ArgMatches<'a>, config: &Config,
                        org: &str, project: &str) -> CliResult<()> {
    let mut resp = config.api_request(
        Method::Get, &format!("/projects/{}/{}/releases/", org, project))?;
    if !resp.status.is_success() {
        fail!(resp);
    } else {
        let infos : Vec<ReleaseInfo> = serde_json::from_reader(&mut resp)?;
        for info in infos {
            println!("[{}] {}: {} ({} new groups)",
                     info.date_released.unwrap_or("              unreleased".into()),
                     info.version,
                     info.reference.unwrap_or("-".into()),
                     info.new_groups);
        }
    }
    Ok(())
}

pub fn execute_files_list<'a>(_matches: &ArgMatches<'a>, config: &Config,
                              org: &str, project: &str, release: &str) -> CliResult<()> {
    for artifact in list_files(config, org, project, release)? {
        println!("{}  ({} bytes)", artifact.name, artifact.size);
    }
    Ok(())
}

pub fn execute_files_delete<'a>(matches: &ArgMatches<'a>, config: &Config,
                                org: &str, project: &str, release: &str) -> CliResult<()> {
    let files : HashSet<String> = match matches.values_of("names") {
        Some(paths) => paths.map(|x| x.into()).collect(),
        None => HashSet::new(),
    };
    for file in list_files(config, org, project, release)? {
        if !(matches.is_present("all") || files.contains(&file.name)) {
            continue;
        }
        let resp = config.api_request(
            Method::Delete, &format!("/projects/{}/{}/releases/{}/files/{}/",
                                     org, project, release, file.id))?;
        if resp.status == StatusCode::NotFound {
            continue;
        } else if !resp.status.is_success() {
            fail!(resp);
        } else {
            println!("D {}", file.name);
        }
    }
    Ok(())
}

pub fn execute_files_upload<'a>(matches: &ArgMatches<'a>, config: &Config,
                                org: &str, project: &str, version: &str) -> CliResult<()> {
    let path = Path::new(matches.value_of("path").unwrap());
    let name = match matches.value_of("name") {
        Some(name) => name,
        None => Path::new(path).file_name()
            .and_then(|x| x.to_str()).ok_or("No filename provided.")?,
    };
    if let Some(artifact) = upload_file(config, org, project, &version,
                                        &path, &name)? {
        println!("A {}  ({} bytes)", artifact.sha1, artifact.size);
    } else {
        fail!("File already present!");
    }
    Ok(())
}

pub fn execute_files_upload_sourcemaps<'a>(matches: &ArgMatches<'a>, config: &Config,
                                           org: &str, project: &str, version: &str) -> CliResult<()> {
    let mut resp = config.api_request(
        Method::Get, &format!("/projects/{}/{}/releases/{}/", org, project, version))?;
    if !resp.status.is_success() {
        fail!(resp);
    }
    let release : ReleaseInfo = serde_json::from_reader(&mut resp)?;
    let url_prefix = matches.value_of("url_prefix").unwrap().trim_right_matches("/");
    let paths = matches.values_of("paths").unwrap();
    let extensions = match matches.values_of("extensions") {
        Some(matches) => matches.map(|ext| OsStr::new(ext.trim_left_matches("."))).collect(),
        None => vec![OsStr::new("js"), OsStr::new("map")],
    };

    println!("Uploading sourcemaps for release {}", release.version);

    for path in paths {
        let path = PathBuf::from(&path);
        for dent in WalkDir::new(&path) {
            let dent = dent?;
            let extension = dent.path().extension();
            if !extensions.iter().any(|ext| Some(*ext) == extension) {
                continue;
            }
            let local_path = dent.path().strip_prefix(&path).unwrap();
            let url = format!("{}/{}", url_prefix, local_path.display());
            println!("{} -> {}", local_path.display(), url);
            if let Some(artifact) = upload_file(config, org, project, &release.version,
                                                dent.path(), &url)? {
                println!("  {}  ({} bytes)", artifact.sha1, artifact.size);
            } else {
                println!("  already present");
            }
        }
    }
    Ok(())
}

pub fn execute_files<'a>(matches: &ArgMatches<'a>, config: &Config,
                         org: &str, project: &str) -> CliResult<()> {
    let release = matches.value_of("version").unwrap();
    if let Some(sub_matches) = matches.subcommand_matches("list") {
        return execute_files_list(sub_matches, config, org, project, release);
    }
    if let Some(sub_matches) = matches.subcommand_matches("delete") {
        return execute_files_delete(sub_matches, config, org, project, release);
    }
    if let Some(sub_matches) = matches.subcommand_matches("upload") {
        return execute_files_upload(sub_matches, config, org, project, release);
    }
    if let Some(sub_matches) = matches.subcommand_matches("upload-sourcemaps") {
        return execute_files_upload_sourcemaps(sub_matches, config, org, project, release);
    }
    unreachable!();
}

pub fn execute<'a>(matches: &ArgMatches<'a>, config: &Config) -> CliResult<()> {
    if let Some(sub_matches) = matches.subcommand_matches("new") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_new(sub_matches, config, &org, &project);
    }
    if let Some(sub_matches) = matches.subcommand_matches("delete") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_delete(sub_matches, config, &org, &project);
    }
    if let Some(sub_matches) = matches.subcommand_matches("list") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_list(sub_matches, config, &org, &project);
    }
    if let Some(sub_matches) = matches.subcommand_matches("files") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_files(sub_matches, config, &org, &project);
    }
    unreachable!();
}
<commit_msg>Added the ability to upload individual files with the sourcemap command<commit_after>use std::path::{Path, PathBuf};
use std::ffi::OsStr;
use std::collections::HashSet;

use clap::{App, AppSettings, Arg, ArgMatches};
use hyper::method::Method;
use hyper::status::StatusCode;
use multipart::client::Multipart;
use mime::{Mime, TopLevel};
use mime_guess::guess_mime_type_opt;
use serde_json;
use walkdir::WalkDir;

use CliResult;
use commands::Config;
use utils::make_subcommand;

#[derive(Debug, Serialize)]
struct NewRelease {
    version: String,
    #[serde(rename="ref", skip_serializing_if="Option::is_none")]
    reference: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    url: Option<String>
}

#[derive(Debug, Deserialize)]
struct ReleaseInfo {
    version: String,
    #[serde(rename="ref")]
    reference: Option<String>,
    url: Option<String>,
    #[serde(rename="dateCreated")]
    date_created: String,
    #[serde(rename="dateReleased")]
    date_released: Option<String>,
    #[serde(rename="newGroups")]
    new_groups: u64,
}

#[derive(Debug, Deserialize)]
struct Artifact {
    id: String,
    sha1: String,
    name: String,
    size: u64,
}

fn get_content_type(mime: Mime) -> String {
    format!("{}{}", mime, match mime {
        Mime(TopLevel::Text, _, _) => {
            "; charset=utf-8"
        },
        _ => "",
    })
}

fn upload_file(config: &Config, org: &str, project: &str, version: &str,
               local_path: &Path, name: &str) -> CliResult<Option<Artifact>> {
    let req = config.prepare_api_request(Method::Post,
        &format!("/projects/{}/{}/releases/{}/files/", org, project, version))?;
    let mut mp = Multipart::from_request_sized(req)?;
    let mimetype = guess_mime_type_opt(local_path).or_else(|| {
        guess_mime_type_opt(&Path::new(name))
    }).or_else(|| {
        "application/octet-stream".parse().ok()
    }).unwrap();
    mp.write_file("file", &local_path)?;
    mp.write_text("header", &format!("Content-Type:{}", get_content_type(mimetype)))?;
    mp.write_text("name", name)?;
    let mut resp = mp.send()?;
    if resp.status == StatusCode::Conflict {
        Ok(None)
    } else if !resp.status.is_success() {
        fail!(resp);
    } else {
        Ok(Some(serde_json::from_reader(&mut resp)?))
    }
}

fn list_files(config: &Config, org: &str, project: &str, release: &str) -> CliResult<Vec<Artifact>> {
    let mut resp = config.api_request(
        Method::Get, &format!("/projects/{}/{}/releases/{}/files/", org, project, release))?;
    // XXX: handle pagination
    if !resp.status.is_success() {
        fail!(resp);
    }
    Ok(serde_json::from_reader(&mut resp)?)
}

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b>
{
    app
        .about("manage releases on Sentry")
        .setting(AppSettings::SubcommandRequiredElseHelp)
        .arg(Arg::with_name("org")
             .value_name("ORG")
             .long("org")
             .short("o")
             .help("The organization slug"))
        .arg(Arg::with_name("project")
             .value_name("PROJECT")
             .long("project")
             .short("p")
             .help("The project slug"))
        .subcommand(make_subcommand("new")
            .about("Create a new release")
            .arg(Arg::with_name("version")
                 .value_name("VERSION")
                 .required(true)
                 .index(1)
                 .help("The version identifier for this release"))
            .arg(Arg::with_name("ref")
                 .long("ref")
                 .value_name("REF")
                 .help("Optional commit reference (commit hash)"))
            .arg(Arg::with_name("url")
                 .long("url")
                 .value_name("URL")
                 .help("Optional URL to the release for information purposes")))
        .subcommand(make_subcommand("delete")
            .about("Delete a release")
            .arg(Arg::with_name("version")
                 .value_name("VERSION")
                 .required(true)
                 .index(1)
                 .help("The version to delete")))
        .subcommand(make_subcommand("list")
            .about("list the most recent releases"))
        .subcommand(make_subcommand("files")
            .about("manage release artifact files")
            .setting(AppSettings::SubcommandRequiredElseHelp)
            .arg(Arg::with_name("version")
                 .value_name("VERSION")
                 .required(true)
                 .index(1)
                 .help("The release to manage the files of"))
            .subcommand(make_subcommand("list")
                .about("List all release files"))
            .subcommand(make_subcommand("delete")
                .about("Delete a release file")
                .arg(Arg::with_name("all")
                     .short("A")
                     .long("all")
                     .help("deletes all files"))
                .arg(Arg::with_name("names")
                     .value_name("NAMES")
                     .index(1)
                     .multiple(true)
                     .help("a list of filenames to delete.")))
            .subcommand(make_subcommand("upload")
                .about("Uploads a file for a given release")
                .arg(Arg::with_name("path")
                     .value_name("PATH")
                     .index(1)
                     .required(true)
                     .help("The file to upload"))
                .arg(Arg::with_name("name")
                     .index(2)
                     .value_name("NAME")
                     .help("The name of the file on the server.")))
            .subcommand(make_subcommand("upload-sourcemaps")
                .about("Uploads sourcemap information for a given release")
                .arg(Arg::with_name("paths")
                     .value_name("PATHS")
                     .index(1)
                     .required(true)
                     .multiple(true)
                     .help("The files to upload"))
                .arg(Arg::with_name("url_prefix")
                     .short("u")
                     .long("url-prefix")
                     .required(true)
                     .value_name("PREFIX")
                     .help("The URL prefix to prepend to all filenames"))
                .arg(Arg::with_name("extensions")
                     .long("ext")
                     .short("x")
                     .multiple(true)
                     .help("Add a file extension to the list of files to upload."))))
}

pub fn execute_new<'a>(matches: &ArgMatches<'a>, config: &Config,
                       org: &str, project: &str) -> CliResult<()> {
    let release = NewRelease {
        version: matches.value_of("version").unwrap().to_owned(),
        reference: matches.value_of("ref").map(|x| x.to_owned()),
        url: matches.value_of("url").map(|x| x.to_owned()),
    };
    let mut resp = config.json_api_request(
        Method::Post, &format!("/projects/{}/{}/releases/", org, project),
        &release)?;
    if !resp.status.is_success() {
        fail!(resp);
    } else {
        let info_rv : ReleaseInfo = serde_json::from_reader(&mut resp)?;
        println!("Created release {}.", info_rv.version);
    }
    Ok(())
}

pub fn execute_delete<'a>(matches: &ArgMatches<'a>, config: &Config,
                          org: &str, project: &str) -> CliResult<()> {
    let version = matches.value_of("version").unwrap();
    let resp = config.api_request(
        Method::Delete, &format!("/projects/{}/{}/releases/{}/", org, project, version))?;
    if resp.status == StatusCode::NotFound {
        println!("Did nothing. Release with this version ({}) does not exist.", version);
    } else if !resp.status.is_success() {
        fail!(resp);
    } else {
        println!("Deleted release {}!", version);
    }
    Ok(())
}

pub fn execute_list<'a>(_matches: &ArgMatches<'a>, config: &Config,
                        org: &str, project: &str) -> CliResult<()> {
    let mut resp = config.api_request(
        Method::Get, &format!("/projects/{}/{}/releases/", org, project))?;
    if !resp.status.is_success() {
        fail!(resp);
    } else {
        let infos : Vec<ReleaseInfo> = serde_json::from_reader(&mut resp)?;
        for info in infos {
            println!("[{}] {}: {} ({} new groups)",
                     info.date_released.unwrap_or("              unreleased".into()),
                     info.version,
                     info.reference.unwrap_or("-".into()),
                     info.new_groups);
        }
    }
    Ok(())
}

pub fn execute_files_list<'a>(_matches: &ArgMatches<'a>, config: &Config,
                              org: &str, project: &str, release: &str) -> CliResult<()> {
    for artifact in list_files(config, org, project, release)? {
        println!("{}  ({} bytes)", artifact.name, artifact.size);
    }
    Ok(())
}

pub fn execute_files_delete<'a>(matches: &ArgMatches<'a>, config: &Config,
                                org: &str, project: &str, release: &str) -> CliResult<()> {
    let files : HashSet<String> = match matches.values_of("names") {
        Some(paths) => paths.map(|x| x.into()).collect(),
        None => HashSet::new(),
    };
    for file in list_files(config, org, project, release)? {
        if !(matches.is_present("all") || files.contains(&file.name)) {
            continue;
        }
        let resp = config.api_request(
            Method::Delete, &format!("/projects/{}/{}/releases/{}/files/{}/",
                                     org, project, release, file.id))?;
        if resp.status == StatusCode::NotFound {
            continue;
        } else if !resp.status.is_success() {
            fail!(resp);
        } else {
            println!("D {}", file.name);
        }
    }
    Ok(())
}

pub fn execute_files_upload<'a>(matches: &ArgMatches<'a>, config: &Config,
                                org: &str, project: &str, version: &str) -> CliResult<()> {
    let path = Path::new(matches.value_of("path").unwrap());
    let name = match matches.value_of("name") {
        Some(name) => name,
        None => Path::new(path).file_name()
            .and_then(|x| x.to_str()).ok_or("No filename provided.")?,
    };
    if let Some(artifact) = upload_file(config, org, project, &version,
                                        &path, &name)? {
        println!("A {}  ({} bytes)", artifact.sha1, artifact.size);
    } else {
        fail!("File already present!");
    }
    Ok(())
}

pub fn execute_files_upload_sourcemaps<'a>(matches: &ArgMatches<'a>, config: &Config,
                                           org: &str, project: &str, version: &str) -> CliResult<()> {
    let mut resp = config.api_request(
        Method::Get, &format!("/projects/{}/{}/releases/{}/", org, project, version))?;
    if !resp.status.is_success() {
        fail!(resp);
    }
    let release : ReleaseInfo = serde_json::from_reader(&mut resp)?;
    let url_prefix = matches.value_of("url_prefix").unwrap().trim_right_matches("/");
    let paths = matches.values_of("paths").unwrap();
    let extensions = match matches.values_of("extensions") {
        Some(matches) => matches.map(|ext| OsStr::new(ext.trim_left_matches("."))).collect(),
        None => vec![OsStr::new("js"), OsStr::new("map")],
    };

    println!("Uploading sourcemaps for release {}", release.version);

    for path in paths {
        // if we start walking over something that is an actual file then
        // the directory iterator yields that path and terminates.  We
        // handle that case here specifically to figure out what the path is
        // we should strip off.
        let walk_path = PathBuf::from(&path);
        let base_path = if walk_path.is_file() {
            walk_path.parent().unwrap()
        } else {
            walk_path.as_path()
        };

        for dent in WalkDir::new(&walk_path) {
            let dent = dent?;
            let extension = dent.path().extension();
            if !extensions.iter().any(|ext| Some(*ext) == extension) {
                continue;
            }
            let local_path = dent.path().strip_prefix(&base_path).unwrap();
            let url = format!("{}/{}", url_prefix, local_path.display());
            println!("{} -> {}", local_path.display(), url);
            if let Some(artifact) = upload_file(config, org, project, &release.version,
                                                dent.path(), &url)? {
                println!("  {}  ({} bytes)", artifact.sha1, artifact.size);
            } else {
                println!("  already present");
            }
        }
    }

    Ok(())
}

pub fn execute_files<'a>(matches: &ArgMatches<'a>, config: &Config,
                         org: &str, project: &str) -> CliResult<()> {
    let release = matches.value_of("version").unwrap();
    if let Some(sub_matches) = matches.subcommand_matches("list") {
        return execute_files_list(sub_matches, config, org, project, release);
    }
    if let Some(sub_matches) = matches.subcommand_matches("delete") {
        return execute_files_delete(sub_matches, config, org, project, release);
    }
    if let Some(sub_matches) = matches.subcommand_matches("upload") {
        return execute_files_upload(sub_matches, config, org, project, release);
    }
    if let Some(sub_matches) = matches.subcommand_matches("upload-sourcemaps") {
        return execute_files_upload_sourcemaps(sub_matches, config, org, project, release);
    }
    unreachable!();
}

pub fn execute<'a>(matches: &ArgMatches<'a>, config: &Config) -> CliResult<()> {
    if let Some(sub_matches) = matches.subcommand_matches("new") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_new(sub_matches, config, &org, &project);
    }
    if let Some(sub_matches) = matches.subcommand_matches("delete") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_delete(sub_matches, config, &org, &project);
    }
    if let Some(sub_matches) = matches.subcommand_matches("list") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_list(sub_matches, config, &org, &project);
    }
    if let Some(sub_matches) = matches.subcommand_matches("files") {
        let (org, project) = config.get_org_and_project(matches)?;
        return execute_files(sub_matches, config, &org, &project);
    }
    unreachable!();
}
<|endoftext|>


<commit_before>use std::io;

use self::reader::LogReader;
use super::*;

pub struct LogIter {
    pub config: Config,
    pub segment_iter: Box<Iterator<Item = (Lsn, LogID)>>,
    pub segment_base: Option<LogID>,
    pub segment_len: usize,
    pub use_compression: bool,
    pub max_lsn: Lsn,
    pub cur_lsn: Lsn,
    pub trailer: Option<Lsn>,
}

impl Iterator for LogIter {
    type Item = (Lsn, LogID, Vec<u8>);

    fn next(&mut self) -> Option<Self::Item> {
        // If segment is None, get next on segment_iter, panic
        // if we can't read something we expect to be able to,
        // return None if there are no more remaining segments.
        loop {
            let remaining_seg_too_small_for_msg =
                !valid_entry_offset(self.cur_lsn as LogID, self.segment_len);

            if self.trailer.is_none() && remaining_seg_too_small_for_msg {
                // We've read to the end of a torn
                // segment and should stop now.
                return None;
            } else if self.segment_base.is_none() ||
                       remaining_seg_too_small_for_msg
            {
                if let Some((next_lsn, next_lid)) = self.segment_iter.next() {
                    assert!(
                        next_lsn + (self.segment_len as Lsn) >= self.cur_lsn,
                        "caller is responsible for providing segments \
                            that contain the initial cur_lsn value or higher"
                    );

                    self.fadvise_willneed(next_lid);

                    if let Err(e) = self.read_segment(next_lsn, next_lid) {
                        debug!(
                            "hit snap while reading segments in \
                            iterator: {:?}",
                            e
                        );
                        return None;
                    }
                } else {
                    return None;
                }
            }

            if self.cur_lsn > self.max_lsn {
                // all done
                return None;
            }

            let lid = self.segment_base.unwrap() +
                (self.cur_lsn % self.segment_len as Lsn) as LogID;

            if self.max_lsn < lid as Lsn {
                // we've hit the end of the log.
                trace!(
                    "in LogIter::next self.max_lsn {} < lid {}",
                    self.max_lsn,
                    lid
                );
                return None;
            }

            if let Ok(f) = self.config.file() {
                match f.read_message(
                    lid,
                    self.segment_len,
                    self.use_compression,
                ) {
                    Ok(LogRead::Flush(lsn, buf, on_disk_len)) => {
                        if lsn != self.cur_lsn {
                            error!("read Flush with bad lsn");
                            return None;
                        }
                        trace!("read flush in LogIter::next");
                        self.cur_lsn += (MSG_HEADER_LEN + on_disk_len) as Lsn;
                        return Some((lsn, lid, buf));
                    }
                    Ok(LogRead::Failed(lsn, on_disk_len)) => {
                        if lsn != self.cur_lsn {
                            error!("read Failed with bad lsn");
                            return None;
                        }
                        trace!("read zeroed in LogIter::next");
                        self.cur_lsn += (MSG_HEADER_LEN + on_disk_len) as Lsn;
                    }
                    Ok(LogRead::Corrupted(_len)) => {
                        trace!("read corrupted end in LogIter::next");
                        return None;
                    }
                    Ok(LogRead::Pad(lsn)) => {
                        if lsn != self.cur_lsn {
                            error!("read Pad with bad lsn");
                            return None;
                        }

                        if self.trailer.is_none() {
                            // This segment was torn, nothing left to read.
                            return None;
                        }

                        self.segment_base.take();

                        self.trailer.take();
                        continue;
                    }
                    Err(e) => {
                        error!(
                            "failed to read log message during iteration: {}",
                            e
                        );
                        return None;
                    }
                }
            } else {
                return None;
            }
        }
    }
}

impl LogIter {
    /// read a segment of log messages. Only call after
    /// pausing segment rewriting on the segment accountant!
    fn read_segment(&mut self, lsn: Lsn, offset: LogID) -> CacheResult<(), ()> {
        trace!(
            "LogIter::read_segment lsn: {:?} cur_lsn: {:?}",
            lsn,
            self.cur_lsn
        );
        // we add segment_len to this check because we may be getting the
        // initial segment that is a bit behind where we left off before.
        assert!(lsn + self.segment_len as Lsn >= self.cur_lsn);
        let f = self.config.file()?;
        let segment_header = f.read_segment_header(offset)?;
        if offset % self.segment_len as LogID != 0 {
            debug!("segment offset not divisible by segment length");
            return Err(Error::Corruption {
                at: offset,
            });
        }
        if segment_header.lsn % self.segment_len as Lsn != 0 {
            debug!(
                "expected a segment header lsn that is divisible \
            by the io_buf_size ({}) instead it was {}",
                self.segment_len,
                segment_header.lsn
            );
            return Err(Error::Corruption {
                at: offset,
            });
        }

        if segment_header.lsn != lsn {
            // this page was torn, nothing to read
            error!(
                "segment header lsn ({}) != expected lsn ({})",
                segment_header.lsn,
                lsn
            );
            return Err(
                io::Error::new(
                    io::ErrorKind::Other,
                    "encountered torn segment",
                ).into(),
            );
        }

        let trailer_offset = offset + self.segment_len as LogID -
            SEG_TRAILER_LEN as LogID;
        let trailer_lsn = segment_header.lsn + self.segment_len as Lsn -
            SEG_TRAILER_LEN as Lsn;

        trace!("trying to read trailer from {}", trailer_offset);
        let segment_trailer = f.read_segment_trailer(trailer_offset);

        trace!("read segment header {:?}", segment_header);
        trace!("read segment trailer {:?}", segment_trailer);

        let trailer_lsn = segment_trailer.ok().and_then(|st| if st.ok &&
            st.lsn == trailer_lsn
        {
            Some(st.lsn)
        } else {
            None
        });

        self.trailer = trailer_lsn;
        self.cur_lsn = segment_header.lsn + SEG_HEADER_LEN as Lsn;
        self.segment_base = Some(offset);

        Ok(())
    }

    fn fadvise_willneed(&self, lid: LogID) {
        #[cfg(target_os = "linux")]
        {
            use std::os::unix::io::AsRawFd;

            if let Ok(f) = self.config.file() {
                let ret = unsafe {
                    libc::posix_fadvise(
                        f.as_raw_fd(),
                        lid as libc::off_t,
                        self.config.io_buf_size as libc::off_t,
                        libc::POSIX_FADV_WILLNEED,
                    )
                };
                if ret != 0 {
                    panic!(
                        "failed to call fadvise: {}",
                        std::io::Error::from_raw_os_error(ret)
                    );
                }
            }
        }
    }
}

fn valid_entry_offset(lid: LogID, segment_len: usize) -> bool {
    let seg_start = lid / segment_len as LogID * segment_len as LogID;

    let max_lid = seg_start + segment_len as LogID -
        SEG_TRAILER_LEN as LogID - MSG_HEADER_LEN as LogID;

    let min_lid = seg_start + SEG_HEADER_LEN as LogID;

    lid >= min_lid && lid <= max_lid
}
<commit_msg>mark the whole fadvise function as linux-only, along with the caller, instead of just the body<commit_after>use std::io;

use self::reader::LogReader;
use super::*;

pub struct LogIter {
    pub config: Config,
    pub segment_iter: Box<Iterator<Item = (Lsn, LogID)>>,
    pub segment_base: Option<LogID>,
    pub segment_len: usize,
    pub use_compression: bool,
    pub max_lsn: Lsn,
    pub cur_lsn: Lsn,
    pub trailer: Option<Lsn>,
}

impl Iterator for LogIter {
    type Item = (Lsn, LogID, Vec<u8>);

    fn next(&mut self) -> Option<Self::Item> {
        // If segment is None, get next on segment_iter, panic
        // if we can't read something we expect to be able to,
        // return None if there are no more remaining segments.
        loop {
            let remaining_seg_too_small_for_msg =
                !valid_entry_offset(self.cur_lsn as LogID, self.segment_len);

            if self.trailer.is_none() && remaining_seg_too_small_for_msg {
                // We've read to the end of a torn
                // segment and should stop now.
                return None;
            } else if self.segment_base.is_none() ||
                       remaining_seg_too_small_for_msg
            {
                if let Some((next_lsn, next_lid)) = self.segment_iter.next() {
                    assert!(
                        next_lsn + (self.segment_len as Lsn) >= self.cur_lsn,
                        "caller is responsible for providing segments \
                            that contain the initial cur_lsn value or higher"
                    );

                    #[cfg(target_os = "linux")] self.fadvise_willneed(next_lid);

                    if let Err(e) = self.read_segment(next_lsn, next_lid) {
                        debug!(
                            "hit snap while reading segments in \
                            iterator: {:?}",
                            e
                        );
                        return None;
                    }
                } else {
                    return None;
                }
            }

            if self.cur_lsn > self.max_lsn {
                // all done
                return None;
            }

            let lid = self.segment_base.unwrap() +
                (self.cur_lsn % self.segment_len as Lsn) as LogID;

            if self.max_lsn < lid as Lsn {
                // we've hit the end of the log.
                trace!(
                    "in LogIter::next self.max_lsn {} < lid {}",
                    self.max_lsn,
                    lid
                );
                return None;
            }

            if let Ok(f) = self.config.file() {
                match f.read_message(
                    lid,
                    self.segment_len,
                    self.use_compression,
                ) {
                    Ok(LogRead::Flush(lsn, buf, on_disk_len)) => {
                        if lsn != self.cur_lsn {
                            error!("read Flush with bad lsn");
                            return None;
                        }
                        trace!("read flush in LogIter::next");
                        self.cur_lsn += (MSG_HEADER_LEN + on_disk_len) as Lsn;
                        return Some((lsn, lid, buf));
                    }
                    Ok(LogRead::Failed(lsn, on_disk_len)) => {
                        if lsn != self.cur_lsn {
                            error!("read Failed with bad lsn");
                            return None;
                        }
                        trace!("read zeroed in LogIter::next");
                        self.cur_lsn += (MSG_HEADER_LEN + on_disk_len) as Lsn;
                    }
                    Ok(LogRead::Corrupted(_len)) => {
                        trace!("read corrupted end in LogIter::next");
                        return None;
                    }
                    Ok(LogRead::Pad(lsn)) => {
                        if lsn != self.cur_lsn {
                            error!("read Pad with bad lsn");
                            return None;
                        }

                        if self.trailer.is_none() {
                            // This segment was torn, nothing left to read.
                            return None;
                        }

                        self.segment_base.take();

                        self.trailer.take();
                        continue;
                    }
                    Err(e) => {
                        error!(
                            "failed to read log message during iteration: {}",
                            e
                        );
                        return None;
                    }
                }
            } else {
                return None;
            }
        }
    }
}

impl LogIter {
    /// read a segment of log messages. Only call after
    /// pausing segment rewriting on the segment accountant!
    fn read_segment(&mut self, lsn: Lsn, offset: LogID) -> CacheResult<(), ()> {
        trace!(
            "LogIter::read_segment lsn: {:?} cur_lsn: {:?}",
            lsn,
            self.cur_lsn
        );
        // we add segment_len to this check because we may be getting the
        // initial segment that is a bit behind where we left off before.
        assert!(lsn + self.segment_len as Lsn >= self.cur_lsn);
        let f = self.config.file()?;
        let segment_header = f.read_segment_header(offset)?;
        if offset % self.segment_len as LogID != 0 {
            debug!("segment offset not divisible by segment length");
            return Err(Error::Corruption {
                at: offset,
            });
        }
        if segment_header.lsn % self.segment_len as Lsn != 0 {
            debug!(
                "expected a segment header lsn that is divisible \
            by the io_buf_size ({}) instead it was {}",
                self.segment_len,
                segment_header.lsn
            );
            return Err(Error::Corruption {
                at: offset,
            });
        }

        if segment_header.lsn != lsn {
            // this page was torn, nothing to read
            error!(
                "segment header lsn ({}) != expected lsn ({})",
                segment_header.lsn,
                lsn
            );
            return Err(
                io::Error::new(
                    io::ErrorKind::Other,
                    "encountered torn segment",
                ).into(),
            );
        }

        let trailer_offset = offset + self.segment_len as LogID -
            SEG_TRAILER_LEN as LogID;
        let trailer_lsn = segment_header.lsn + self.segment_len as Lsn -
            SEG_TRAILER_LEN as Lsn;

        trace!("trying to read trailer from {}", trailer_offset);
        let segment_trailer = f.read_segment_trailer(trailer_offset);

        trace!("read segment header {:?}", segment_header);
        trace!("read segment trailer {:?}", segment_trailer);

        let trailer_lsn = segment_trailer.ok().and_then(|st| if st.ok &&
            st.lsn == trailer_lsn
        {
            Some(st.lsn)
        } else {
            None
        });

        self.trailer = trailer_lsn;
        self.cur_lsn = segment_header.lsn + SEG_HEADER_LEN as Lsn;
        self.segment_base = Some(offset);

        Ok(())
    }

    #[cfg(target_os = "linux")]
    fn fadvise_willneed(&self, lid: LogID) {
        use std::os::unix::io::AsRawFd;

        if let Ok(f) = self.config.file() {
            let ret = unsafe {
                libc::posix_fadvise(
                    f.as_raw_fd(),
                    lid as libc::off_t,
                    self.config.io_buf_size as libc::off_t,
                    libc::POSIX_FADV_WILLNEED,
                )
            };
            if ret != 0 {
                panic!(
                    "failed to call fadvise: {}",
                    std::io::Error::from_raw_os_error(ret)
                );
            }
        }
    }
}

fn valid_entry_offset(lid: LogID, segment_len: usize) -> bool {
    let seg_start = lid / segment_len as LogID * segment_len as LogID;

    let max_lid = seg_start + segment_len as LogID -
        SEG_TRAILER_LEN as LogID - MSG_HEADER_LEN as LogID;

    let min_lid = seg_start + SEG_HEADER_LEN as LogID;

    lid >= min_lid && lid <= max_lid
}
<|endoftext|>


<commit_before>//! This module defines `AssistCtx` -- the API surface that is exposed to assists.
use hir::Semantics;
use ra_db::FileRange;
use ra_fmt::{leading_indent, reindent};
use ra_ide_db::RootDatabase;
use ra_syntax::{
    algo::{self, find_covering_element, find_node_at_offset},
    AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
    TokenAtOffset,
};
use ra_text_edit::TextEditBuilder;

use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
use algo::SyntaxRewriter;

#[derive(Clone, Debug)]
pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);

#[derive(Clone, Debug)]
pub(crate) struct AssistInfo {
    pub(crate) label: AssistLabel,
    pub(crate) group_label: Option<GroupLabel>,
    pub(crate) action: Option<AssistAction>,
}

impl AssistInfo {
    fn new(label: AssistLabel) -> AssistInfo {
        AssistInfo { label, group_label: None, action: None }
    }

    fn resolved(self, action: AssistAction) -> AssistInfo {
        AssistInfo { action: Some(action), ..self }
    }

    fn with_group(self, group_label: GroupLabel) -> AssistInfo {
        AssistInfo { group_label: Some(group_label), ..self }
    }

    pub(crate) fn into_resolved(self) -> Option<ResolvedAssist> {
        let label = self.label;
        let group_label = self.group_label;
        self.action.map(|action| ResolvedAssist { label, group_label, action })
    }
}

pub(crate) type AssistHandler = fn(AssistCtx) -> Option<Assist>;

/// `AssistCtx` allows to apply an assist or check if it could be applied.
///
/// Assists use a somewhat over-engineered approach, given the current needs. The
/// assists workflow consists of two phases. In the first phase, a user asks for
/// the list of available assists. In the second phase, the user picks a
/// particular assist and it gets applied.
///
/// There are two peculiarities here:
///
/// * first, we ideally avoid computing more things then necessary to answer
///   "is assist applicable" in the first phase.
/// * second, when we are applying assist, we don't have a guarantee that there
///   weren't any changes between the point when user asked for assists and when
///   they applied a particular assist. So, when applying assist, we need to do
///   all the checks from scratch.
///
/// To avoid repeating the same code twice for both "check" and "apply"
/// functions, we use an approach reminiscent of that of Django's function based
/// views dealing with forms. Each assist receives a runtime parameter,
/// `should_compute_edit`. It first check if an edit is applicable (potentially
/// computing info required to compute the actual edit). If it is applicable,
/// and `should_compute_edit` is `true`, it then computes the actual edit.
///
/// So, to implement the original assists workflow, we can first apply each edit
/// with `should_compute_edit = false`, and then applying the selected edit
/// again, with `should_compute_edit = true` this time.
///
/// Note, however, that we don't actually use such two-phase logic at the
/// moment, because the LSP API is pretty awkward in this place, and it's much
/// easier to just compute the edit eagerly :-)
#[derive(Clone)]
pub(crate) struct AssistCtx<'a> {
    pub(crate) sema: &'a Semantics<'a, RootDatabase>,
    pub(crate) db: &'a RootDatabase,
    pub(crate) frange: FileRange,
    source_file: SourceFile,
    should_compute_edit: bool,
}

impl<'a> AssistCtx<'a> {
    pub fn new(
        sema: &'a Semantics<'a, RootDatabase>,
        frange: FileRange,
        should_compute_edit: bool,
    ) -> AssistCtx<'a> {
        let source_file = sema.parse(frange.file_id);
        AssistCtx { sema, db: sema.db, frange, source_file, should_compute_edit }
    }

    pub(crate) fn add_assist(
        self,
        id: AssistId,
        label: impl Into<String>,
        f: impl FnOnce(&mut ActionBuilder),
    ) -> Option<Assist> {
        let label = AssistLabel::new(id, label.into());

        let mut info = AssistInfo::new(label);
        if self.should_compute_edit {
            let action = {
                let mut edit = ActionBuilder::new(&self);
                f(&mut edit);
                edit.build()
            };
            info = info.resolved(action)
        };

        Some(Assist(vec![info]))
    }

    pub(crate) fn add_assist_group(self, group_name: impl Into<String>) -> AssistGroup<'a> {
        AssistGroup { ctx: self, group_name: group_name.into(), assists: Vec::new() }
    }

    pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
        self.source_file.syntax().token_at_offset(self.frange.range.start())
    }

    pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
        self.token_at_offset().find(|it| it.kind() == kind)
    }

    pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
        find_node_at_offset(self.source_file.syntax(), self.frange.range.start())
    }

    pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
        self.sema
            .find_node_at_offset_with_descend(self.source_file.syntax(), self.frange.range.start())
    }

    pub(crate) fn covering_element(&self) -> SyntaxElement {
        find_covering_element(self.source_file.syntax(), self.frange.range)
    }
    pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
        find_covering_element(self.source_file.syntax(), range)
    }
}

pub(crate) struct AssistGroup<'a> {
    ctx: AssistCtx<'a>,
    group_name: String,
    assists: Vec<AssistInfo>,
}

impl<'a> AssistGroup<'a> {
    pub(crate) fn add_assist(
        &mut self,
        id: AssistId,
        label: impl Into<String>,
        f: impl FnOnce(&mut ActionBuilder),
    ) {
        let label = AssistLabel::new(id, label.into());

        let mut info = AssistInfo::new(label).with_group(GroupLabel(self.group_name.clone()));
        if self.ctx.should_compute_edit {
            let action = {
                let mut edit = ActionBuilder::new(&self.ctx);
                f(&mut edit);
                edit.build()
            };
            info = info.resolved(action)
        };

        self.assists.push(info)
    }

    pub(crate) fn finish(self) -> Option<Assist> {
        if self.assists.is_empty() {
            None
        } else {
            Some(Assist(self.assists))
        }
    }
}

pub(crate) struct ActionBuilder<'a, 'b> {
    edit: TextEditBuilder,
    cursor_position: Option<TextSize>,
    target: Option<TextRange>,
    file: AssistFile,
    ctx: &'a AssistCtx<'b>,
}

impl<'a, 'b> ActionBuilder<'a, 'b> {
    fn new(ctx: &'a AssistCtx<'b>) -> Self {
        Self {
            edit: TextEditBuilder::default(),
            cursor_position: None,
            target: None,
            file: AssistFile::default(),
            ctx,
        }
    }

    pub(crate) fn ctx(&self) -> &AssistCtx<'b> {
        &self.ctx
    }

    /// Replaces specified `range` of text with a given string.
    pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
        self.edit.replace(range, replace_with.into())
    }

    /// Replaces specified `node` of text with a given string, reindenting the
    /// string to maintain `node`'s existing indent.
    // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent
    pub(crate) fn replace_node_and_indent(
        &mut self,
        node: &SyntaxNode,
        replace_with: impl Into<String>,
    ) {
        let mut replace_with = replace_with.into();
        if let Some(indent) = leading_indent(node) {
            replace_with = reindent(&replace_with, &indent)
        }
        self.replace(node.text_range(), replace_with)
    }

    /// Remove specified `range` of text.
    #[allow(unused)]
    pub(crate) fn delete(&mut self, range: TextRange) {
        self.edit.delete(range)
    }

    /// Append specified `text` at the given `offset`
    pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
        self.edit.insert(offset, text.into())
    }

    /// Specify desired position of the cursor after the assist is applied.
    pub(crate) fn set_cursor(&mut self, offset: TextSize) {
        self.cursor_position = Some(offset)
    }

    /// Specify that the assist should be active withing the `target` range.
    ///
    /// Target ranges are used to sort assists: the smaller the target range,
    /// the more specific assist is, and so it should be sorted first.
    pub(crate) fn target(&mut self, target: TextRange) {
        self.target = Some(target)
    }

    /// Get access to the raw `TextEditBuilder`.
    pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
        &mut self.edit
    }

    pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
        algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
    }
    pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
        let node = rewriter.rewrite_root().unwrap();
        let new = rewriter.rewrite(&node);
        algo::diff(&node, &new).into_text_edit(&mut self.edit)
    }

    pub(crate) fn set_file(&mut self, assist_file: AssistFile) {
        self.file = assist_file
    }

    fn build(self) -> AssistAction {
        AssistAction {
            edit: self.edit.finish(),
            cursor_position: self.cursor_position,
            target: self.target,
            file: self.file,
        }
    }
}
<commit_msg>Minor rename<commit_after>//! This module defines `AssistCtx` -- the API surface that is exposed to assists.
use hir::Semantics;
use ra_db::FileRange;
use ra_fmt::{leading_indent, reindent};
use ra_ide_db::RootDatabase;
use ra_syntax::{
    algo::{self, find_covering_element, find_node_at_offset},
    AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
    TokenAtOffset,
};
use ra_text_edit::TextEditBuilder;

use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
use algo::SyntaxRewriter;

#[derive(Clone, Debug)]
pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);

#[derive(Clone, Debug)]
pub(crate) struct AssistInfo {
    pub(crate) label: AssistLabel,
    pub(crate) group_label: Option<GroupLabel>,
    pub(crate) action: Option<AssistAction>,
}

impl AssistInfo {
    fn new(label: AssistLabel) -> AssistInfo {
        AssistInfo { label, group_label: None, action: None }
    }

    fn resolved(self, action: AssistAction) -> AssistInfo {
        AssistInfo { action: Some(action), ..self }
    }

    fn with_group(self, group_label: GroupLabel) -> AssistInfo {
        AssistInfo { group_label: Some(group_label), ..self }
    }

    pub(crate) fn into_resolved(self) -> Option<ResolvedAssist> {
        let label = self.label;
        let group_label = self.group_label;
        self.action.map(|action| ResolvedAssist { label, group_label, action })
    }
}

pub(crate) type AssistHandler = fn(AssistCtx) -> Option<Assist>;

/// `AssistCtx` allows to apply an assist or check if it could be applied.
///
/// Assists use a somewhat over-engineered approach, given the current needs. The
/// assists workflow consists of two phases. In the first phase, a user asks for
/// the list of available assists. In the second phase, the user picks a
/// particular assist and it gets applied.
///
/// There are two peculiarities here:
///
/// * first, we ideally avoid computing more things then necessary to answer
///   "is assist applicable" in the first phase.
/// * second, when we are applying assist, we don't have a guarantee that there
///   weren't any changes between the point when user asked for assists and when
///   they applied a particular assist. So, when applying assist, we need to do
///   all the checks from scratch.
///
/// To avoid repeating the same code twice for both "check" and "apply"
/// functions, we use an approach reminiscent of that of Django's function based
/// views dealing with forms. Each assist receives a runtime parameter,
/// `should_compute_edit`. It first check if an edit is applicable (potentially
/// computing info required to compute the actual edit). If it is applicable,
/// and `should_compute_edit` is `true`, it then computes the actual edit.
///
/// So, to implement the original assists workflow, we can first apply each edit
/// with `should_compute_edit = false`, and then applying the selected edit
/// again, with `should_compute_edit = true` this time.
///
/// Note, however, that we don't actually use such two-phase logic at the
/// moment, because the LSP API is pretty awkward in this place, and it's much
/// easier to just compute the edit eagerly :-)
#[derive(Clone)]
pub(crate) struct AssistCtx<'a> {
    pub(crate) sema: &'a Semantics<'a, RootDatabase>,
    pub(crate) db: &'a RootDatabase,
    pub(crate) frange: FileRange,
    source_file: SourceFile,
    should_compute_edit: bool,
}

impl<'a> AssistCtx<'a> {
    pub fn new(
        sema: &'a Semantics<'a, RootDatabase>,
        frange: FileRange,
        should_compute_edit: bool,
    ) -> AssistCtx<'a> {
        let source_file = sema.parse(frange.file_id);
        AssistCtx { sema, db: sema.db, frange, source_file, should_compute_edit }
    }

    pub(crate) fn add_assist(
        self,
        id: AssistId,
        label: impl Into<String>,
        f: impl FnOnce(&mut ActionBuilder),
    ) -> Option<Assist> {
        let label = AssistLabel::new(id, label.into());

        let mut info = AssistInfo::new(label);
        if self.should_compute_edit {
            let action = {
                let mut edit = ActionBuilder::new(&self);
                f(&mut edit);
                edit.build()
            };
            info = info.resolved(action)
        };

        Some(Assist(vec![info]))
    }

    pub(crate) fn add_assist_group(self, group_name: impl Into<String>) -> AssistGroup<'a> {
        AssistGroup { ctx: self, group: group_name.into(), assists: Vec::new() }
    }

    pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
        self.source_file.syntax().token_at_offset(self.frange.range.start())
    }

    pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
        self.token_at_offset().find(|it| it.kind() == kind)
    }

    pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
        find_node_at_offset(self.source_file.syntax(), self.frange.range.start())
    }

    pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
        self.sema
            .find_node_at_offset_with_descend(self.source_file.syntax(), self.frange.range.start())
    }

    pub(crate) fn covering_element(&self) -> SyntaxElement {
        find_covering_element(self.source_file.syntax(), self.frange.range)
    }
    pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
        find_covering_element(self.source_file.syntax(), range)
    }
}

pub(crate) struct AssistGroup<'a> {
    ctx: AssistCtx<'a>,
    group: String,
    assists: Vec<AssistInfo>,
}

impl<'a> AssistGroup<'a> {
    pub(crate) fn add_assist(
        &mut self,
        id: AssistId,
        label: impl Into<String>,
        f: impl FnOnce(&mut ActionBuilder),
    ) {
        let label = AssistLabel::new(id, label.into());

        let mut info = AssistInfo::new(label).with_group(GroupLabel(self.group.clone()));
        if self.ctx.should_compute_edit {
            let action = {
                let mut edit = ActionBuilder::new(&self.ctx);
                f(&mut edit);
                edit.build()
            };
            info = info.resolved(action)
        };

        self.assists.push(info)
    }

    pub(crate) fn finish(self) -> Option<Assist> {
        if self.assists.is_empty() {
            None
        } else {
            Some(Assist(self.assists))
        }
    }
}

pub(crate) struct ActionBuilder<'a, 'b> {
    edit: TextEditBuilder,
    cursor_position: Option<TextSize>,
    target: Option<TextRange>,
    file: AssistFile,
    ctx: &'a AssistCtx<'b>,
}

impl<'a, 'b> ActionBuilder<'a, 'b> {
    fn new(ctx: &'a AssistCtx<'b>) -> Self {
        Self {
            edit: TextEditBuilder::default(),
            cursor_position: None,
            target: None,
            file: AssistFile::default(),
            ctx,
        }
    }

    pub(crate) fn ctx(&self) -> &AssistCtx<'b> {
        &self.ctx
    }

    /// Replaces specified `range` of text with a given string.
    pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
        self.edit.replace(range, replace_with.into())
    }

    /// Replaces specified `node` of text with a given string, reindenting the
    /// string to maintain `node`'s existing indent.
    // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent
    pub(crate) fn replace_node_and_indent(
        &mut self,
        node: &SyntaxNode,
        replace_with: impl Into<String>,
    ) {
        let mut replace_with = replace_with.into();
        if let Some(indent) = leading_indent(node) {
            replace_with = reindent(&replace_with, &indent)
        }
        self.replace(node.text_range(), replace_with)
    }

    /// Remove specified `range` of text.
    #[allow(unused)]
    pub(crate) fn delete(&mut self, range: TextRange) {
        self.edit.delete(range)
    }

    /// Append specified `text` at the given `offset`
    pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
        self.edit.insert(offset, text.into())
    }

    /// Specify desired position of the cursor after the assist is applied.
    pub(crate) fn set_cursor(&mut self, offset: TextSize) {
        self.cursor_position = Some(offset)
    }

    /// Specify that the assist should be active withing the `target` range.
    ///
    /// Target ranges are used to sort assists: the smaller the target range,
    /// the more specific assist is, and so it should be sorted first.
    pub(crate) fn target(&mut self, target: TextRange) {
        self.target = Some(target)
    }

    /// Get access to the raw `TextEditBuilder`.
    pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
        &mut self.edit
    }

    pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
        algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
    }
    pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
        let node = rewriter.rewrite_root().unwrap();
        let new = rewriter.rewrite(&node);
        algo::diff(&node, &new).into_text_edit(&mut self.edit)
    }

    pub(crate) fn set_file(&mut self, assist_file: AssistFile) {
        self.file = assist_file
    }

    fn build(self) -> AssistAction {
        AssistAction {
            edit: self.edit.finish(),
            cursor_position: self.cursor_position,
            target: self.target,
            file: self.file,
        }
    }
}
<|endoftext|>


<commit_before>use std::fmt;
use std::error;

use parsers::ParseError;

#[derive(Debug, Clone, PartialEq)]
pub enum AttributeError {
    // parse error
    Parse (ParseError),

    // invalid value
    Value (String)
}

#[derive(Debug, Clone, PartialEq)]
pub struct NodeError {
    attr_name: String,
    err:       AttributeError
}

impl NodeError {
    pub fn parse_error (attr_name: &str, error: ParseError) -> NodeError {
        NodeError {
            attr_name: attr_name.to_string (),
            err: AttributeError::Parse (error)
        }
    }

    pub fn value_error (attr_name: &str, description: String) -> NodeError {
        NodeError {
            attr_name: attr_name.to_string (),
            err: AttributeError::Value (description)
        }
    }

    pub fn attribute_error (attr_name: &str, error: AttributeError) -> NodeError {
        NodeError {
            attr_name: attr_name.to_string (),
            err: error
        }
    }
}

impl error::Error for NodeError {
    fn description (&self) -> &str {
        match self.err {
            AttributeError::Parse (_) => &"parse error",
            AttributeError::Value (_) => &"invalid attribute value"
        }
    }
}

impl fmt::Display for NodeError {
    fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.err {
            AttributeError::Parse (ref n) => write! (f,
                                                     "error parsing value for attribute \"{}\": {}",
                                                     self.attr_name,
                                                     n.display),

            AttributeError::Value (ref s) => write! (f,
                                                     "invalid value for attribute \"{}\": {}",
                                                     self.attr_name,
                                                     s)
        }
    }
}

impl From<ParseError> for AttributeError {
    fn from (pe: ParseError) -> AttributeError {
        AttributeError::Parse (pe)
    }
}

pub fn is_parse_error<T> (r: &Result<T, AttributeError>) -> bool {
    match *r {
        Err (AttributeError::Parse (_)) => true,
        _ => false
    }
}

pub fn is_value_error<T> (r: &Result<T, AttributeError>) -> bool {
    match *r {
        Err (AttributeError::Value (_)) => true,
        _ => false
    }
}
<commit_msg>Make NodeError::value_error() take two &str arguments, for convenience<commit_after>use std::fmt;
use std::error;

use parsers::ParseError;

#[derive(Debug, Clone, PartialEq)]
pub enum AttributeError {
    // parse error
    Parse (ParseError),

    // invalid value
    Value (String)
}

#[derive(Debug, Clone, PartialEq)]
pub struct NodeError {
    attr_name: String,
    err:       AttributeError
}

impl NodeError {
    pub fn parse_error (attr_name: &str, error: ParseError) -> NodeError {
        NodeError {
            attr_name: attr_name.to_string (),
            err: AttributeError::Parse (error)
        }
    }

    pub fn value_error (attr_name: &str, description: &str) -> NodeError {
        NodeError {
            attr_name: attr_name.to_string (),
            err: AttributeError::Value (description.to_string ())
        }
    }

    pub fn attribute_error (attr_name: &str, error: AttributeError) -> NodeError {
        NodeError {
            attr_name: attr_name.to_string (),
            err: error
        }
    }
}

impl error::Error for NodeError {
    fn description (&self) -> &str {
        match self.err {
            AttributeError::Parse (_) => &"parse error",
            AttributeError::Value (_) => &"invalid attribute value"
        }
    }
}

impl fmt::Display for NodeError {
    fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.err {
            AttributeError::Parse (ref n) => write! (f,
                                                     "error parsing value for attribute \"{}\": {}",
                                                     self.attr_name,
                                                     n.display),

            AttributeError::Value (ref s) => write! (f,
                                                     "invalid value for attribute \"{}\": {}",
                                                     self.attr_name,
                                                     s)
        }
    }
}

impl From<ParseError> for AttributeError {
    fn from (pe: ParseError) -> AttributeError {
        AttributeError::Parse (pe)
    }
}

pub fn is_parse_error<T> (r: &Result<T, AttributeError>) -> bool {
    match *r {
        Err (AttributeError::Parse (_)) => true,
        _ => false
    }
}

pub fn is_value_error<T> (r: &Result<T, AttributeError>) -> bool {
    match *r {
        Err (AttributeError::Value (_)) => true,
        _ => false
    }
}
<|endoftext|>