1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/*!
This crate provides a pager that can be used to page through results from the YT API.

# Example usage

```rust,no_run
# fn create<T>() -> T { unimplemented!() }
# use self::youtube3_util::ItemPager;
# use self::youtube3_util::youtube3::YouTube;
# use self::youtube3_util::{hyper, yup_oauth2 as oauth2};
# let hub: YouTube<hyper::Client, oauth2::Authenticator<oauth2::DefaultAuthenticatorDelegate, oauth2::DiskTokenStorage, hyper::Client>> = create();
let pager = ItemPager::new(&hub, Box::new(|hub|
	hub.playlist_items()
		.list("snippet")
		.playlist_id("test")
        // minimize the amount of network requests by setting a large page size
		.max_results(50)
)).unwrap();

for playlist_item in pager {
	println!("Item: {:?}", playlist_item);
}
```
*/

pub extern crate google_youtube3 as youtube3;
use youtube3::*;
use youtube3::Result as YtResult;
pub extern crate hyper;
use hyper::Client;
pub extern crate yup_oauth2;
use yup_oauth2::GetToken;

use std::borrow::BorrowMut;
use std::fmt::Debug;

macro_rules! gen_list_impl {
    ($call:ident => $call_resp:ident => $item:ident) => (
        impl<'a, C, A> ListCall for $call<'a, C, A>
        where
            C: BorrowMut<Client>,
            A: GetToken {
            type Response = $call_resp;

            fn page_token(self, token: &str) -> Self {
                self.page_token(token)
            }
            fn doit(self) -> YtResult<Self::Response> {
                (self as $call<C, A>).doit().map(|x| x.1)
            }
        }

        impl ListCallResponse for $call_resp {
            type Item = $item;

            fn next_page_token(&self) -> Option<String> {
                self.next_page_token.clone()
            }

            fn items(&self) -> Vec<Self::Item> {
                self.items.clone().expect("youtube forgot to send items in an API response")
            }
        }

        impl ListItem for $item {

        }
    )
}

gen_list_impl!(PlaylistItemListCall => PlaylistItemListResponse => PlaylistItem);
gen_list_impl!(SubscriptionListCall => SubscriptionListResponse => Subscription);
gen_list_impl!(LiveChatMessageListCall => LiveChatMessageListResponse => LiveChatMessage);
gen_list_impl!(VideoListCall => VideoListResponse => Video);
gen_list_impl!(ChannelListCall => ChannelListResponse => Channel);
gen_list_impl!(LiveBroadcastListCall => LiveBroadcastListResponse => LiveBroadcast);
gen_list_impl!(PlaylistListCall => PlaylistListResponse => Playlist);
gen_list_impl!(CommentListCall => CommentListResponse => Comment);
gen_list_impl!(LiveStreamListCall => LiveStreamListResponse => LiveStream);
gen_list_impl!(SponsorListCall => SponsorListResponse => Sponsor);
gen_list_impl!(FanFundingEventListCall => FanFundingEventListResponse => FanFundingEvent);
gen_list_impl!(ActivityListCall => ActivityListResponse => Activity);
gen_list_impl!(LiveChatModeratorListCall => LiveChatModeratorListResponse => LiveChatModerator);
gen_list_impl!(CommentThreadListCall => CommentThreadListResponse => CommentThread);
gen_list_impl!(SearchListCall => SearchListResponse => SearchResult);
// these two don't support pagination even though they return page tokens??? 
//gen_list_impl!(VideoCategoryListCall => VideoCategoryListResponse => VideoCategory);
//gen_list_impl!(GuideCategoryListCall => GuideCategoryListResponse => GuideCategory);

pub trait ListCall: CallBuilder {
    type Response: ListCallResponse;

    fn page_token(self, token: &str) -> Self;
    fn doit(self) -> YtResult<Self::Response>;
}

pub trait ListCallResponse: ResponseResult + Debug {
    type Item: ListItem;

    fn next_page_token(&self) -> Option<String>;
    fn items(&self) -> Vec<Self::Item>;
}

pub trait ListItem: Clone + Debug {

}

pub struct ItemPager<'a, C: 'a, A: 'a, Call: ListCall> {
	hub: &'a YouTube<C, A>,
	next_page_token: Option<String>,
	current_results: Vec<<<Call as ListCall>::Response as ListCallResponse>::Item>,
	base_call: Box<Fn(&'a YouTube<C, A>) -> Call>
}

impl<'a, C, A, Call> ItemPager<'a, C, A, Call> 
where
    C: BorrowMut<Client>,
    A: GetToken,
    Call: ListCall {

    /// Construct a new ItemPager. The results of the provided function will be used as the base for every request.
	pub fn new(hub: &'a YouTube<C, A>, base: Box<Fn(&'a YouTube<C, A>) -> Call>) -> YtResult<Self> {
		let response = base(hub)
			.doit()?;

        let mut items = response.items();
        items.reverse();
		Ok(ItemPager {
			hub: hub,
			next_page_token: response.next_page_token(),
			current_results: items,
			base_call: base
		})
	}

    /// Has to be called with self.next_page_token == Some(...)
	fn next_page(&mut self) -> YtResult<()> {
		let reponse = (self.base_call)(self.hub)
			.page_token(&self.next_page_token.take().unwrap())
			.doit()?;
		self.next_page_token = reponse.next_page_token();
		self.current_results = reponse.items();
        self.current_results.reverse();
        Ok(())
	}
}

impl<'a, C, A, Call,> Iterator for ItemPager<'a, C, A, Call>
where
    C: BorrowMut<Client>,
    A: GetToken,
    Call: ListCall {
	type Item = YtResult<<<Call as ListCall>::Response as ListCallResponse>::Item>;

    /// After this returns an error, no further items will be returned (even if there would still be some).
	fn next(&mut self) -> Option<Self::Item> {
		if let Some(item) = self.current_results.pop() {
			Some(Ok(item))
		} else if self.next_page_token.is_some() {
			// get next page
			if let Err(e) = self.next_page() {
                Some(Err(e))
            } else {
			    self.next()
            }
		} else {
			None
		}
	}
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        // cant test this without an authenticated yt client,
        // which requires a secret..
    }
}