servo/src/components/net/resource_task.rs
2013-10-10 18:18:39 -07:00

154 lines
4.4 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! A task that takes a URL and streams back the binary data.
use file_loader;
use http_loader;
use std::cell::Cell;
use std::comm::{Chan, Port, SharedChan};
use extra::url::Url;
use util::spawn_listener;
pub enum ControlMsg {
/// Request the data associated with a particular URL
Load(Url, Chan<ProgressMsg>),
Exit
}
/// Messages sent in response to a `Load` message
#[deriving(Eq)]
pub enum ProgressMsg {
/// URL changed due to a redirect. There can be zero or more of these,
/// but they are guaranteed to arrive before messages of any other type.
UrlChange(Url),
/// Binary data - there may be multiple of these
Payload(~[u8]),
/// Indicates loading is complete, either successfully or not
Done(Result<(), ()>)
}
/// Handle to a resource task
pub type ResourceTask = SharedChan<ControlMsg>;
pub type LoaderTask = ~fn(url: Url, Chan<ProgressMsg>);
/**
Creates a task to load a specific resource
The ResourceManager delegates loading to a different type of loader task for
each URL scheme
*/
type LoaderTaskFactory = extern "Rust" fn() -> LoaderTask;
/// Create a ResourceTask with the default loaders
pub fn ResourceTask() -> ResourceTask {
let loaders = ~[
(~"file", file_loader::factory),
(~"http", http_loader::factory),
];
create_resource_task_with_loaders(loaders)
}
fn create_resource_task_with_loaders(loaders: ~[(~str, LoaderTaskFactory)]) -> ResourceTask {
let loaders_cell = Cell::new(loaders);
let chan = do spawn_listener |from_client| {
// TODO: change copy to move once we can move out of closures
ResourceManager(from_client, loaders_cell.take()).start()
};
SharedChan::new(chan)
}
pub struct ResourceManager {
from_client: Port<ControlMsg>,
/// Per-scheme resource loaders
loaders: ~[(~str, LoaderTaskFactory)],
}
pub fn ResourceManager(from_client: Port<ControlMsg>,
loaders: ~[(~str, LoaderTaskFactory)]) -> ResourceManager {
ResourceManager {
from_client : from_client,
loaders : loaders,
}
}
impl ResourceManager {
fn start(&self) {
loop {
match self.from_client.recv() {
Load(url, progress_chan) => {
self.load(url.clone(), progress_chan)
}
Exit => {
break
}
}
}
}
fn load(&self, url: Url, progress_chan: Chan<ProgressMsg>) {
match self.get_loader_factory(&url) {
Some(loader_factory) => {
debug!("resource_task: loading url: %s", url.to_str());
loader_factory(url, progress_chan);
}
None => {
debug!("resource_task: no loader for scheme %s", url.scheme);
progress_chan.send(Done(Err(())));
}
}
}
fn get_loader_factory(&self, url: &Url) -> Option<LoaderTask> {
for scheme_loader in self.loaders.iter() {
match *scheme_loader {
(ref scheme, ref loader_factory) => {
if (*scheme) == url.scheme {
return Some((*loader_factory)());
}
}
}
}
return None;
}
}
#[test]
fn test_exit() {
let resource_task = ResourceTask();
resource_task.send(Exit);
}
#[test]
fn test_bad_scheme() {
let resource_task = ResourceTask();
let progress = Port();
resource_task.send(Load(url::from_str(~"bogus://whatever").get(), progress.chan()));
match progress.recv() {
Done(result) => { assert!(result.is_err()) }
_ => fail
}
resource_task.send(Exit);
}
#[test]
fn should_delegate_to_scheme_loader() {
let payload = ~[1, 2, 3];
let loader_factory = |_url: Url, progress_chan: Chan<ProgressMsg>| {
progress_chan.send(Payload(payload.clone()));
progress_chan.send(Done(Ok(())));
};
let loader_factories = ~[(~"snicklefritz", loader_factory)];
let resource_task = create_resource_task_with_loaders(loader_factories);
let progress = Port();
resource_task.send(Load(url::from_str(~"snicklefritz://heya").get(), progress.chan()));
assert!(progress.recv() == Payload(payload));
assert!(progress.recv() == Done(Ok(())));
resource_task.send(Exit);
}