Skip to content

Commit 42a76e4

Browse files
committed
feat(cmn): ContentRange header (parse and format)
Now we are able to send the transfer-update requests and implement the actual chunk logic.
1 parent 556906c commit 42a76e4

3 files changed

Lines changed: 142 additions & 3 deletions

File tree

src/mako/lib/mbuild.mako

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ else {
791791
url: url,
792792
reader: &mut reader,
793793
media_type: reader_mime_type.clone(),
794-
content_size: size
794+
content_length: size
795795
}.upload()
796796
};
797797
match upload_result {

src/rust/cmn.rs

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use std::marker::MarkerTrait;
22
use std::io::{self, Read, Seek, Cursor, Write, SeekFrom};
33
use std;
4+
use std::fmt;
5+
use std::str::FromStr;
46

57
use mime::{Mime, TopLevel, SubLevel, Attr, Value};
68
use oauth2;
79
use oauth2::TokenType;
810
use hyper;
9-
use hyper::header::{ContentType, ContentLength, Headers, UserAgent, Authorization};
11+
use hyper::header::{ContentType, ContentLength, Headers, UserAgent, Authorization, Header,
12+
HeaderFormat};
1013
use hyper::http::LINE_ENDING;
1114
use hyper::method::Method;
1215

@@ -361,6 +364,107 @@ impl_header!(XUploadContentType,
361364
"X-Upload-Content-Type",
362365
Mime);
363366

367+
#[derive(Clone, PartialEq, Debug)]
368+
pub enum ByteRange {
369+
Any,
370+
Chunk(u64, u64)
371+
}
372+
373+
impl fmt::Display for ByteRange {
374+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
375+
match *self {
376+
ByteRange::Any => fmt.write_str("*").ok(),
377+
ByteRange::Chunk(first, last) => write!(fmt, "{}-{}", first, last).ok()
378+
};
379+
Ok(())
380+
}
381+
}
382+
383+
impl FromStr for ByteRange {
384+
type Err = &'static str;
385+
386+
/// NOTE: only implements `%i-%i`, not `*`
387+
fn from_str(s: &str) -> std::result::Result<ByteRange, &'static str> {
388+
let parts: Vec<&str> = s.split('-').collect();
389+
if parts.len() != 2 {
390+
return Err("Expected two parts: %i-%i")
391+
}
392+
Ok(
393+
ByteRange::Chunk(
394+
match FromStr::from_str(parts[0]) {
395+
Ok(d) => d,
396+
_ => return Err("Couldn't parse 'first' as digit")
397+
},
398+
match FromStr::from_str(parts[1]) {
399+
Ok(d) => d,
400+
_ => return Err("Couldn't parse 'last' as digit")
401+
}
402+
)
403+
)
404+
}
405+
}
406+
407+
/// Implements the Content-Range header, for serialization only
408+
#[derive(Clone, PartialEq, Debug)]
409+
pub struct ContentRange {
410+
pub range: ByteRange,
411+
pub total_length: u64,
412+
}
413+
414+
impl Header for ContentRange {
415+
fn header_name() -> &'static str {
416+
"Content-Range"
417+
}
418+
419+
/// We are not parsable, as parsing is done by the `Range` header
420+
fn parse_header(raw: &[Vec<u8>]) -> Option<ContentRange> {
421+
None
422+
}
423+
}
424+
425+
426+
impl HeaderFormat for ContentRange {
427+
fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
428+
write!(fmt, "bytes {}/{}", self.range, self.total_length).ok();
429+
Ok(())
430+
}
431+
}
432+
433+
#[derive(Clone, PartialEq, Debug)]
434+
pub struct RangeResponseHeader(pub ByteRange);
435+
436+
impl Header for RangeResponseHeader {
437+
fn header_name() -> &'static str {
438+
"Range"
439+
}
440+
441+
fn parse_header(raw: &[Vec<u8>]) -> Option<RangeResponseHeader> {
442+
match raw {
443+
[ref v] => {
444+
if let Ok(s) = std::str::from_utf8(v) {
445+
if s.starts_with("bytes=") {
446+
return Some(RangeResponseHeader(
447+
match FromStr::from_str(&s[6..]) {
448+
Ok(br) => br,
449+
_ => return None
450+
}
451+
))
452+
}
453+
}
454+
None
455+
},
456+
_ => None
457+
}
458+
}
459+
}
460+
461+
impl HeaderFormat for RangeResponseHeader {
462+
/// No implmentation necessary, we just need to parse
463+
fn fmt_header(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
464+
Err(fmt::Error)
465+
}
466+
}
467+
364468
/// A utility type to perform a resumable upload from start to end.
365469
pub struct ResumableUploadHelper<'a, NC: 'a, A: 'a> {
366470
pub client: &'a mut hyper::client::Client<NC>,
@@ -371,7 +475,7 @@ pub struct ResumableUploadHelper<'a, NC: 'a, A: 'a> {
371475
pub url: &'a str,
372476
pub reader: &'a mut ReadSeek,
373477
pub media_type: Mime,
374-
pub content_size: u64
478+
pub content_length: u64
375479
}
376480

377481
impl<'a, NC, A> ResumableUploadHelper<'a, NC, A>
@@ -381,6 +485,7 @@ impl<'a, NC, A> ResumableUploadHelper<'a, NC, A>
381485
fn query_transfer_status(&'a mut self) -> (u64, hyper::HttpResult<hyper::client::Response>) {
382486
self.client.post(self.url)
383487
.header(UserAgent(self.user_agent.to_string()))
488+
.header(ContentRange { range: ByteRange::Any, total_length: self.content_length } )
384489
.header(self.auth_header.clone());
385490
(0, Err(hyper::error::HttpError::HttpStatusError))
386491
}

src/rust/lib.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![allow(dead_code, deprecated, unused_features, unused_variables, unused_imports)]
33
//! library with code shared by all generated implementations
44
#![plugin(serde_macros)]
5+
#[macro_use]
56
extern crate hyper;
67
extern crate mime;
78
extern crate "yup-oauth2" as oauth2;
@@ -19,6 +20,8 @@ mod tests {
1920
use std::io::Read;
2021
use std::default::Default;
2122
use std::old_path::BytesContainer;
23+
use hyper;
24+
use std::str::FromStr;
2225

2326
use serde::json;
2427

@@ -121,4 +124,35 @@ bar\r\n\
121124
// let j = "{\"snooSnoo\":\"foo\",\"foo\":\"bar\"}";
122125
// let b: BarOpt = json::from_str(&j).unwrap();
123126
}
127+
128+
#[test]
129+
fn content_range() {
130+
for &(ref c, ref expected) in
131+
&[(ContentRange {range: ByteRange::Any, total_length: 50 }, "Content-Range: bytes */50\r\n"),
132+
(ContentRange {range: ByteRange::Chunk(23, 40), total_length: 45},
133+
"Content-Range: bytes 23-40/45\r\n")] {
134+
let mut headers = hyper::header::Headers::new();
135+
headers.set(c.clone());
136+
assert_eq!(headers.to_string(), expected.to_string());
137+
}
138+
}
139+
140+
#[test]
141+
fn byte_range_from_str() {
142+
assert_eq!(<ByteRange as FromStr>::from_str("2-42"),
143+
Ok(ByteRange::Chunk(2, 42)))
144+
}
145+
146+
#[test]
147+
fn parse_range_response() {
148+
let r: RangeResponseHeader = hyper::header::Header::parse_header(&[b"bytes=2-42".to_vec()]).unwrap();
149+
150+
match r.0 {
151+
ByteRange::Chunk(f, l) => {
152+
assert_eq!(f, 2);
153+
assert_eq!(l, 42);
154+
}
155+
_ => unreachable!()
156+
}
157+
}
124158
}

0 commit comments

Comments
 (0)