diff options
Diffstat (limited to 'src/servo-net/image_cache_task.rs')
-rw-r--r-- | src/servo-net/image_cache_task.rs | 1074 |
1 files changed, 1074 insertions, 0 deletions
diff --git a/src/servo-net/image_cache_task.rs b/src/servo-net/image_cache_task.rs new file mode 100644 index 00000000000..4a462cdc926 --- /dev/null +++ b/src/servo-net/image_cache_task.rs @@ -0,0 +1,1074 @@ +/* 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/. */ + +use image::base::{Image, load_from_memory}; +use resource_task; +use resource_task::ResourceTask; +use servo_util::url::{UrlMap, url_map}; + +use clone_arc = std::arc::clone; +use core::cell::Cell; +use core::comm::{Chan, Port, SharedChan, stream}; +use core::task::spawn; +use core::to_str::ToStr; +use core::util::replace; +use std::arc::ARC; +use std::net::url::Url; + +pub enum Msg { + /// Tell the cache that we may need a particular image soon. Must be posted + /// before Decode + Prefetch(Url), + + // FIXME: We can probably get rid of this Cell now + /// Used be the prefetch tasks to post back image binaries + priv StorePrefetchedImageData(Url, Result<Cell<~[u8]>, ()>), + + /// Tell the cache to decode an image. Must be posted before GetImage/WaitForImage + Decode(Url), + + /// Used by the decoder tasks to post decoded images back to the cache + priv StoreImage(Url, Option<ARC<~Image>>), + + /// Request an Image object for a URL. If the image is not is not immediately + /// available then ImageNotReady is returned. + GetImage(Url, Chan<ImageResponseMsg>), + + /// Wait for an image to become available (or fail to load). + WaitForImage(Url, Chan<ImageResponseMsg>), + + /// For testing + priv OnMsg(~fn(msg: &Msg)), + + /// Clients must wait for a response before shutting down the ResourceTask + Exit(Chan<()>), +} + +pub enum ImageResponseMsg { + ImageReady(ARC<~Image>), + ImageNotReady, + ImageFailed +} + +impl ImageResponseMsg { + fn clone(&self) -> ImageResponseMsg { + match *self { + ImageReady(ref img) => ImageReady(clone_arc(img)), + ImageNotReady => ImageNotReady, + ImageFailed => ImageFailed, + } + } +} + +impl Eq for ImageResponseMsg { + fn eq(&self, other: &ImageResponseMsg) -> bool { + // FIXME: Bad copies + match (self.clone(), other.clone()) { + (ImageReady(*), ImageReady(*)) => fail!(~"unimplemented comparison"), + (ImageNotReady, ImageNotReady) => true, + (ImageFailed, ImageFailed) => true, + + (ImageReady(*), _) | (ImageNotReady, _) | (ImageFailed, _) => false + } + } + + fn ne(&self, other: &ImageResponseMsg) -> bool { + !(*self).eq(other) + } +} + +pub type ImageCacheTask = SharedChan<Msg>; + +type DecoderFactory = ~fn() -> ~fn(&[u8]) -> Option<Image>; + +pub fn ImageCacheTask(resource_task: ResourceTask) -> ImageCacheTask { + ImageCacheTask_(resource_task, default_decoder_factory) +} + +pub fn ImageCacheTask_(resource_task: ResourceTask, decoder_factory: DecoderFactory) + -> ImageCacheTask { + // FIXME: Doing some dancing to avoid copying decoder_factory, our test + // version of which contains an uncopyable type which rust will currently + // copy unsoundly + let decoder_factory_cell = Cell(decoder_factory); + + let (port, chan) = stream(); + let chan = SharedChan::new(chan); + let port_cell = Cell(port); + let chan_cell = Cell(chan.clone()); + + do spawn { + let mut cache = ImageCache { + resource_task: resource_task.clone(), + decoder_factory: decoder_factory_cell.take(), + port: port_cell.take(), + chan: chan_cell.take(), + state_map: url_map(), + wait_map: url_map(), + need_exit: None + }; + cache.run(); + } + + chan +} + +fn SyncImageCacheTask(resource_task: ResourceTask) -> ImageCacheTask { + let (port, chan) = stream(); + let port_cell = Cell(port); + + do spawn { + let port = port_cell.take(); + let inner_cache = ImageCacheTask(resource_task.clone()); + + loop { + let msg: Msg = port.recv(); + + match msg { + GetImage(url, response) => { + inner_cache.send(WaitForImage(url, response)); + } + Exit(response) => { + inner_cache.send(Exit(response)); + break; + } + msg => inner_cache.send(msg) + } + } + } + + SharedChan::new(chan) +} + +struct ImageCache { + /// A handle to the resource task for fetching the image binaries + resource_task: ResourceTask, + /// Creates image decoders + decoder_factory: DecoderFactory, + /// The port on which we'll receive client requests + port: Port<Msg>, + /// A copy of the shared chan to give to child tasks + chan: SharedChan<Msg>, + /// The state of processsing an image for a URL + state_map: UrlMap<ImageState>, + /// List of clients waiting on a WaitForImage response + wait_map: UrlMap<@mut ~[Chan<ImageResponseMsg>]>, + need_exit: Option<Chan<()>>, +} + +enum ImageState { + Init, + Prefetching(AfterPrefetch), + Prefetched(@Cell<~[u8]>), + Decoding, + Decoded(@ARC<~Image>), + Failed +} + +enum AfterPrefetch { + DoDecode, + DoNotDecode +} + +#[allow(non_implicitly_copyable_typarams)] +impl ImageCache { + pub fn run(&mut self) { + let mut msg_handlers: ~[~fn(msg: &Msg)] = ~[]; + + loop { + let msg = self.port.recv(); + + for msg_handlers.each |handler| { + (*handler)(&msg) + } + + debug!("image_cache_task: received: %?", msg); + + match msg { + Prefetch(url) => self.prefetch(url), + StorePrefetchedImageData(url, data) => { + self.store_prefetched_image_data(url, data); + } + Decode(url) => self.decode(url), + StoreImage(url, image) => self.store_image(url, image), + GetImage(url, response) => self.get_image(url, response), + WaitForImage(url, response) => { + self.wait_for_image(url, response) + } + OnMsg(handler) => msg_handlers.push(handler), + Exit(response) => { + assert!(self.need_exit.is_none()); + self.need_exit = Some(response); + } + } + + let need_exit = replace(&mut self.need_exit, None); + + match need_exit { + Some(response) => { + // Wait until we have no outstanding requests and subtasks + // before exiting + let mut can_exit = true; + for self.state_map.each_value |state| { + match *state { + Prefetching(*) => can_exit = false, + Decoding => can_exit = false, + + Init | Prefetched(*) | Decoded(*) | Failed => () + } + } + + if can_exit { + response.send(()); + break; + } else { + self.need_exit = Some(response); + } + } + None => () + } + } + } + + priv fn get_state(&self, url: Url) -> ImageState { + match self.state_map.find(&url) { + Some(state) => *state, + None => Init + } + } + + priv fn set_state(&self, url: Url, state: ImageState) { + self.state_map.insert(url, state); + } + + priv fn prefetch(&self, url: Url) { + match self.get_state(copy url) { + Init => { + let to_cache = self.chan.clone(); + let resource_task = self.resource_task.clone(); + let url_cell = Cell(copy url); + + do spawn { + let url = url_cell.take(); + debug!("image_cache_task: started fetch for %s", url.to_str()); + + let image = load_image_data(copy url, resource_task.clone()); + + let result = if image.is_ok() { + Ok(Cell(result::unwrap(image))) + } else { + Err(()) + }; + to_cache.send(StorePrefetchedImageData(copy url, result)); + debug!("image_cache_task: ended fetch for %s", (copy url).to_str()); + } + + self.set_state(url, Prefetching(DoNotDecode)); + } + + Prefetching(*) | Prefetched(*) | Decoding | Decoded(*) | Failed => { + // We've already begun working on this image + } + } + } + + priv fn store_prefetched_image_data(&self, url: Url, data: Result<Cell<~[u8]>, ()>) { + match self.get_state(copy url) { + Prefetching(next_step) => { + match data { + Ok(data_cell) => { + let data = data_cell.take(); + self.set_state(copy url, Prefetched(@Cell(data))); + match next_step { + DoDecode => self.decode(url), + _ => () + } + } + Err(*) => { + self.set_state(copy url, Failed); + self.purge_waiters(url, || ImageFailed); + } + } + } + + Init + | Prefetched(*) + | Decoding + | Decoded(*) + | Failed => { + fail!(~"wrong state for storing prefetched image") + } + } + } + + priv fn decode(&self, url: Url) { + match self.get_state(copy url) { + Init => fail!(~"decoding image before prefetch"), + + Prefetching(DoNotDecode) => { + // We don't have the data yet, queue up the decode + self.set_state(url, Prefetching(DoDecode)) + } + + Prefetching(DoDecode) => { + // We don't have the data yet, but the decode request is queued up + } + + Prefetched(data_cell) => { + assert!(!data_cell.is_empty()); + + let data = data_cell.take(); + let to_cache = self.chan.clone(); + let url_cell = Cell(copy url); + let decode = (self.decoder_factory)(); + + do spawn { + let url = url_cell.take(); + debug!("image_cache_task: started image decode for %s", url.to_str()); + let image = decode(data); + let image = if image.is_some() { + Some(ARC(~image.unwrap())) + } else { + None + }; + to_cache.send(StoreImage(copy url, image)); + debug!("image_cache_task: ended image decode for %s", url.to_str()); + } + + self.set_state(url, Decoding); + } + + Decoding | Decoded(*) | Failed => { + // We've already begun decoding + } + } + } + + priv fn store_image(&self, url: Url, image: Option<ARC<~Image>>) { + + match self.get_state(copy url) { + Decoding => { + match image { + Some(image) => { + self.set_state(copy url, Decoded(@clone_arc(&image))); + self.purge_waiters(url, || ImageReady(clone_arc(&image)) ); + } + None => { + self.set_state(copy url, Failed); + self.purge_waiters(url, || ImageFailed ); + } + } + } + + Init + | Prefetching(*) + | Prefetched(*) + | Decoded(*) + | Failed => { + fail!(~"incorrect state in store_image") + } + } + + } + + priv fn purge_waiters(&self, url: Url, f: &fn() -> ImageResponseMsg) { + match self.wait_map.find(&url) { + Some(waiters) => { + let waiters = *waiters; + let mut new_waiters = ~[]; + new_waiters <-> *waiters; + + for new_waiters.each |response| { + response.send(f()); + } + + *waiters <-> new_waiters; + self.wait_map.remove(&url); + } + None => () + } + } + + + priv fn get_image(&self, url: Url, response: Chan<ImageResponseMsg>) { + match self.get_state(copy url) { + Init => fail!(~"request for image before prefetch"), + Prefetching(DoDecode) => response.send(ImageNotReady), + Prefetching(DoNotDecode) | Prefetched(*) => fail!(~"request for image before decode"), + Decoding => response.send(ImageNotReady), + Decoded(image) => response.send(ImageReady(clone_arc(image))), + Failed => response.send(ImageFailed), + } + } + + priv fn wait_for_image(&self, url: Url, response: Chan<ImageResponseMsg>) { + match self.get_state(copy url) { + Init => fail!(~"request for image before prefetch"), + + Prefetching(DoNotDecode) | Prefetched(*) => fail!(~"request for image before decode"), + + Prefetching(DoDecode) | Decoding => { + // We don't have this image yet + match self.wait_map.find(&url) { + Some(waiters) => { + vec::push(*waiters, response); + } + None => { + self.wait_map.insert(url, @mut ~[response]); + } + } + } + + Decoded(image) => { + response.send(ImageReady(clone_arc(image))); + } + + Failed => { + response.send(ImageFailed); + } + } + } + +} + + +trait ImageCacheTaskClient { + fn exit(&self); +} + +impl ImageCacheTaskClient for ImageCacheTask { + fn exit(&self) { + let (response_port, response_chan) = stream(); + self.send(Exit(response_chan)); + response_port.recv(); + } +} + +fn load_image_data(url: Url, resource_task: ResourceTask) -> Result<~[u8], ()> { + let (response_port, response_chan) = stream(); + resource_task.send(resource_task::Load(url, response_chan)); + + let mut image_data = ~[]; + + loop { + match response_port.recv() { + resource_task::Payload(data) => { + image_data += data; + } + resource_task::Done(result::Ok(*)) => { + return Ok(image_data); + } + resource_task::Done(result::Err(*)) => { + return Err(()); + } + } + } +} + +fn default_decoder_factory() -> ~fn(&[u8]) -> Option<Image> { + let foo: ~fn(&[u8]) -> Option<Image> = |data: &[u8]| { load_from_memory(data) }; + foo +} + +#[cfg(test)] +fn mock_resource_task(on_load: ~fn(resource: Chan<resource_task::ProgressMsg>)) -> ResourceTask { + do spawn_listener |port: Port<resource_task::ControlMsg>| { + loop { + match port.recv() { + resource_task::Load(_, response) => { + on_load(response); + } + resource_task::Exit => break + } + } + } +} + +#[test] +fn should_exit_on_request() { + let mock_resource_task = mock_resource_task(|_response| () ); + + let image_cache_task = ImageCacheTask(mock_resource_task); + let _url = make_url(~"file", None); + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +#[should_fail] +fn should_fail_if_unprefetched_image_is_requested() { + let mock_resource_task = mock_resource_task(|_response| () ); + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + let (chan, port) = stream(); + image_cache_task.send(GetImage(url, chan)); + port.recv(); +} + +#[test] +fn should_request_url_from_resource_task_on_prefetch() { + let url_requested = Port(); + let url_requested_chan = url_requested.chan(); + + let mock_resource_task = do mock_resource_task |response| { + url_requested_chan.send(()); + response.send(resource_task::Done(result::Ok(()))); + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + image_cache_task.send(Prefetch(url)); + url_requested.recv(); + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + + +#[test] +#[should_fail] +fn should_fail_if_requesting_decode_of_an_unprefetched_image() { + let mock_resource_task = mock_resource_task(|_response| () ); + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + image_cache_task.send(Decode(url)); + image_cache_task.exit(); +} + +#[test] +#[should_fail] +fn should_fail_if_requesting_image_before_requesting_decode() { + let mock_resource_task = do mock_resource_task |response| { + response.send(resource_task::Done(result::Ok(()))); + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + image_cache_task.send(Prefetch(copy url)); + // no decode message + + let (chan, _port) = stream(); + image_cache_task.send(GetImage(url, chan)); + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +fn should_not_request_url_from_resource_task_on_multiple_prefetches() { + let url_requested = comm::Port(); + let url_requested_chan = url_requested.chan(); + + let mock_resource_task = do mock_resource_task |response| { + url_requested_chan.send(()); + response.send(resource_task::Done(result::Ok(()))); + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Prefetch(url)); + url_requested.recv(); + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); + assert!(!url_requested.peek()) +} + +#[test] +fn should_return_image_not_ready_if_data_has_not_arrived() { + let (wait_chan, wait_port) = pipes::stream(); + + let mock_resource_task = do mock_resource_task |response| { + // Don't send the data until after the client requests + // the image + wait_port.recv(); + response.send(resource_task::Payload(test_image_bin())); + response.send(resource_task::Done(result::Ok(()))); + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Decode(copy url)); + let (response_chan, response_port) = stream(); + image_cache_task.send(GetImage(url, response_chan)); + assert!(response_port.recv() == ImageNotReady); + wait_chan.send(()); + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +fn should_return_decoded_image_data_if_data_has_arrived() { + let mock_resource_task = do mock_resource_task |response| { + response.send(resource_task::Payload(test_image_bin())); + response.send(resource_task::Done(result::Ok(()))); + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + let wait_for_image = comm::Port(); + let wait_for_image_chan = wait_for_image.chan(); + + image_cache_task.send(OnMsg(|msg| { + match *msg { + StoreImage(*) => wait_for_image_chan.send(()), + _ => () + } + })); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Decode(copy url)); + + // Wait until our mock resource task has sent the image to the image cache + wait_for_image_chan.recv(); + + let (response_chan, response_port) = stream(); + image_cache_task.send(GetImage(url, response_chan)); + match response_port.recv() { + ImageReady(_) => (), + _ => fail + } + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +fn should_return_decoded_image_data_for_multiple_requests() { + let mock_resource_task = do mock_resource_task |response| { + response.send(resource_task::Payload(test_image_bin())); + response.send(resource_task::Done(result::Ok(()))); + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + let wait_for_image = comm::Port(); + let wait_for_image_chan = wait_for_image.chan(); + + image_cache_task.send(OnMsg(|msg| { + match *msg { + StoreImage(*) => wait_for_image_chan.send(()), + _ => () + } + })); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Decode(copy url)); + + // Wait until our mock resource task has sent the image to the image cache + wait_for_image.recv(); + + for iter::repeat(2) { + let (response_chan, response_port) = stream(); + image_cache_task.send(GetImage(copy url, response_chan)); + match response_port.recv() { + ImageReady(_) => (), + _ => fail + } + } + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +fn should_not_request_image_from_resource_task_if_image_is_already_available() { + let image_bin_sent = comm::Port(); + let image_bin_sent_chan = image_bin_sent.chan(); + + let resource_task_exited = comm::Port(); + let resource_task_exited_chan = resource_task_exited.chan(); + + let mock_resource_task = do spawn_listener |port: comm::Port<resource_task::ControlMsg>| { + loop { + match port.recv() { + resource_task::Load(_, response) => { + response.send(resource_task::Payload(test_image_bin())); + response.send(resource_task::Done(result::Ok(()))); + image_bin_sent_chan.send(()); + } + resource_task::Exit => { + resource_task_exited_chan.send(()); + break + } + } + } + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + image_cache_task.send(Prefetch(copy url)); + + // Wait until our mock resource task has sent the image to the image cache + image_bin_sent.recv(); + + image_cache_task.send(Prefetch(copy url)); + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); + + resource_task_exited.recv(); + + // Our resource task should not have received another request for the image + // because it's already cached + assert!(!image_bin_sent.peek()); +} + +#[test] +fn should_not_request_image_from_resource_task_if_image_fetch_already_failed() { + let image_bin_sent = comm::Port(); + let image_bin_sent_chan = image_bin_sent.chan(); + + let resource_task_exited = comm::Port(); + let resource_task_exited_chan = resource_task_exited.chan(); + + let mock_resource_task = do spawn_listener |port: comm::Port<resource_task::ControlMsg>| { + loop { + match port.recv() { + resource_task::Load(_, response) => { + response.send(resource_task::Payload(test_image_bin())); + response.send(resource_task::Done(result::Err(()))); + image_bin_sent_chan.send(()); + } + resource_task::Exit => { + resource_task_exited_chan.send(()); + break + } + } + } + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Decode(copy url)); + + // Wait until our mock resource task has sent the image to the image cache + image_bin_sent.recv(); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Decode(copy url)); + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); + + resource_task_exited.recv(); + + // Our resource task should not have received another request for the image + // because it's already cached + assert!(!image_bin_sent.peek()); +} + +#[test] +fn should_return_failed_if_image_bin_cannot_be_fetched() { + let mock_resource_task = do mock_resource_task |response| { + response.send(resource_task::Payload(test_image_bin())); + // ERROR fetching image + response.send(resource_task::Done(result::Err(()))); + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + let wait_for_prefetech = comm::Port(); + let wait_for_prefetech_chan = wait_for_prefetech.chan(); + + image_cache_task.send(OnMsg(|msg| { + match *msg { + StorePrefetchedImageData(*) => wait_for_prefetech_chan.send(()), + _ => () + } + })); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Decode(copy url)); + + // Wait until our mock resource task has sent the image to the image cache + wait_for_prefetech.recv(); + + let (response_chan, response_port) = stream(); + image_cache_task.send(GetImage(url, response_chan)); + match response_port.recv() { + ImageFailed => (), + _ => fail + } + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +fn should_return_failed_for_multiple_get_image_requests_if_image_bin_cannot_be_fetched() { + let mock_resource_task = do mock_resource_task |response | { + response.send(resource_task::Payload(test_image_bin())); + // ERROR fetching image + response.send(resource_task::Done(result::Err(()))); + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + let wait_for_prefetech = comm::Port(); + let wait_for_prefetech_chan = wait_for_prefetech.chan(); + + image_cache_task.send(OnMsg(|msg| { + match *msg { + StorePrefetchedImageData(*) => wait_for_prefetech_chan.send(()), + _ => () + } + })); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Decode(copy url)); + + // Wait until our mock resource task has sent the image to the image cache + wait_for_prefetech.recv(); + + let (response_chan, response_port) = stream(); + image_cache_task.send(GetImage(copy url, response_chan)); + match response_port.recv() { + ImageFailed => (), + _ => fail + } + + // And ask again, we should get the same response + let (response_chan, response_port) = stream(); + image_cache_task.send(GetImage(url, response_chan)); + match response_port.recv() { + ImageFailed => (), + _ => fail + } + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +fn should_return_not_ready_if_image_is_still_decoding() { + let (wait_to_decode_chan, wait_to_decode_port) = pipes::stream(); + + let mock_resource_task = do mock_resource_task |response| { + response.send(resource_task::Payload(test_image_bin())); + response.send(resource_task::Done(result::Ok(()))); + }; + + let wait_to_decode_port_cell = Cell(wait_to_decode_port); + let decoder_factory = || { + let wait_to_decode_port = wait_to_decode_port_cell.take(); + |data: &[u8]| { + // Don't decode until after the client requests the image + wait_to_decode_port.recv(); + load_from_memory(data) + } + }; + + let image_cache_task = ImageCacheTask_(mock_resource_task, decoder_factory); + let url = make_url(~"file", None); + + let wait_for_prefetech = comm::Port(); + let wait_for_prefetech_chan = wait_for_prefetech.chan(); + + image_cache_task.send(OnMsg(|msg| { + match *msg { + StorePrefetchedImageData(*) => wait_for_prefetech_chan.send(()), + _ => () + } + })); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Decode(copy url)); + + // Wait until our mock resource task has sent the image to the image cache + wait_for_prefetech.recv(); + + // Make the request + let (response_chan, response_port) = stream(); + image_cache_task.send(GetImage(url, response_chan)); + + match response_port.recv() { + ImageNotReady => (), + _ => fail + } + + // Now decode + wait_to_decode_chan.send(()); + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +fn should_return_failed_if_image_decode_fails() { + let mock_resource_task = do mock_resource_task |response| { + // Bogus data + response.send(resource_task::Payload(~[])); + response.send(resource_task::Done(result::Ok(()))); + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + let wait_for_decode = comm::Port(); + let wait_for_decode_chan = wait_for_decode.chan(); + + image_cache_task.send(OnMsg(|msg| { + match *msg { + StoreImage(*) => wait_for_decode_chan.send(()), + _ => () + } + })); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Decode(copy url)); + + // Wait until our mock resource task has sent the image to the image cache + wait_for_decode.recv(); + + // Make the request + let (response_chan, response_port) = stream(); + image_cache_task.send(GetImage(url, response_chan)); + + match response_port.recv() { + ImageFailed => (), + _ => fail + } + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +fn should_return_image_on_wait_if_image_is_already_loaded() { + let mock_resource_task = do mock_resource_task |response| { + response.send(resource_task::Payload(test_image_bin())); + response.send(resource_task::Done(result::Ok(()))); + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + let wait_for_decode = comm::Port(); + let wait_for_decode_chan = wait_for_decode.chan(); + + image_cache_task.send(OnMsg(|msg| { + match *msg { + StoreImage(*) => wait_for_decode_chan.send(()), + _ => () + } + })); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Decode(copy url)); + + // Wait until our mock resource task has sent the image to the image cache + wait_for_decode.recv(); + + let (response_chan, response_port) = stream(); + image_cache_task.send(WaitForImage(url, response_chan)); + match response_port.recv() { + ImageReady(*) => (), + _ => fail + } + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +fn should_return_image_on_wait_if_image_is_not_yet_loaded() { + let (wait_chan, wait_port) = pipes::stream(); + + let mock_resource_task = do mock_resource_task |response| { + wait_port.recv(); + response.send(resource_task::Payload(test_image_bin())); + response.send(resource_task::Done(result::Ok(()))); + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Decode(copy url)); + + let (response_chan, response_port) = stream(); + image_cache_task.send(WaitForImage(url, response_chan)); + + wait_chan.send(()); + + match response_port.recv() { + ImageReady(*) => (), + _ => fail + } + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +fn should_return_image_failed_on_wait_if_image_fails_to_load() { + let (wait_chan, wait_port) = pipes::stream(); + + let mock_resource_task = do mock_resource_task |response| { + wait_port.recv(); + response.send(resource_task::Payload(test_image_bin())); + response.send(resource_task::Done(result::Err(()))); + }; + + let image_cache_task = ImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Decode(copy url)); + + let (response_chan, response_port) = stream(); + image_cache_task.send(WaitForImage(url, response_chan)); + + wait_chan.send(()); + + match response_port.recv() { + ImageFailed => (), + _ => fail + } + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + +#[test] +fn sync_cache_should_wait_for_images() { + let mock_resource_task = do mock_resource_task |response| { + response.send(resource_task::Payload(test_image_bin())); + response.send(resource_task::Done(result::Ok(()))); + }; + + let image_cache_task = SyncImageCacheTask(mock_resource_task); + let url = make_url(~"file", None); + + image_cache_task.send(Prefetch(copy url)); + image_cache_task.send(Decode(copy url)); + + let (response_chan, response_port) = stream(); + image_cache_task.send(GetImage(url, response_chan)); + match response_port.recv() { + ImageReady(_) => (), + _ => fail + } + + image_cache_task.exit(); + mock_resource_task.send(resource_task::Exit); +} + |