1
/* Copyright (c) 2006 Google Inc.
3
* Licensed under the Apache License, Version 2.0 (the "License");
4
* you may not use this file except in compliance with the License.
5
* You may obtain a copy of the License at
7
* http://www.apache.org/licenses/LICENSE-2.0
9
* Unless required by applicable law or agreed to in writing, software
10
* distributed under the License is distributed on an "AS IS" BASIS,
11
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
* See the License for the specific language governing permissions and
13
* limitations under the License.
19
using System.Collections;
22
using Google.GData.Client;
23
using Google.GData.Extensions;
24
using System.Collections.Generic;
25
using Google.GData.Extensions.AppControl;
28
namespace Google.GData.Client
30
//////////////////////////////////////////////////////////////////////
31
/// <summary>a generic Feed class
33
//////////////////////////////////////////////////////////////////////
34
public class Feed<T> where T: Entry, new()
39
int numberRetrieved=0;
45
/// default constructor that takes the underlying atomfeed
47
/// <param name="af"></param>
48
public Feed(AtomFeed af)
54
/// constructs a new feed object based on a service and a query
56
/// <param name="service"></param>
57
/// <param name="q"></param>
58
public Feed(Service service, FeedQuery q)
60
this.service = service;
65
/// returns the used feed object
67
/// <returns></returns>
68
public AtomFeed AtomFeed
74
if (this.service != null && this.query != null)
76
this.af = this.service.Query(query);
84
/// if set to true will cause the feed to add more data when you iterate over it's entries
86
/// <returns></returns>
87
public bool AutoPaging
102
/// returns the position in the real feed of the first entry in this feed
104
/// <returns>an int indicating the start in the feed</returns>
105
public int StartIndex
109
if (this.AtomFeed != null)
111
return this.AtomFeed.StartIndex;
118
/// returns the setup paging size of this feed. If you set AutoPaging to true
119
/// this is the size that is used to get more results
121
/// <returns></returns>
126
if (this.AtomFeed != null)
128
return this.AtomFeed.ItemsPerPage;
135
/// returns the number of entries the server believes the feed holds
137
/// <returns></returns>
138
public int TotalResults
142
if (this.AtomFeed != null)
144
return this.AtomFeed.TotalResults;
152
/// the maxium number of entries to be retrieved. This is normally
153
/// setup using the RequestSettings when the feed is constructed.
155
/// <returns></returns>
164
this.maximum = value;
171
returns the initial list of entries.This page is the data
172
you got from the Requestobject and will remain constant.
173
Unless you set AutoPaging to true, in that case:
174
This will go back to the server and fetch data again if
175
needed. Example. If you pagesize is 30, you get an initial set of
176
30 entries. While enumerating, when reaching 30, the code will go
177
to the server and get the next 30 rows. It will continue to do so
178
until the server reports no more rows available.
179
Note that you should cache the entries returned in a list of your own
180
if you want to access them more than once, as this one does no caching on
184
The following code illustrates a possible use of
185
the <c>Entries</c> property:
187
YouTubeRequestSettings settings = new YouTubeRequestSettings("yourApp", "yourClient", "yourKey", "username", "pwd");
188
YouTubeRequest f = new YouTubeRequest(settings);
189
Feed<Playlist> feed = f.GetPlaylistsFeed(null);
190
foreach (Vidoe v in feed.Entries)
195
public IEnumerable<T> Entries
201
// if we have iterated once before, we need to reset
202
if (this.numberRetrieved > 0 && this.paging == false && this.service != null)
206
if (this.AtomFeed == null)
209
this.numberRetrieved = 0;
213
looping = af.NextChunk != null && this.paging == true;
214
foreach (AtomEntry e in af.Entries)
220
this.numberRetrieved++;
223
if (this.Maximum > 0 && this.numberRetrieved >= this.Maximum)
230
FeedQuery q = new FeedQuery(this.AtomFeed.NextChunk);
231
this.af = this.AtomFeed.Service.Query(q);
237
//end of public class Feed
241
/// the Entry class is the base class for all Feed of T type feeds
242
/// it encapsulates the AtomEntry
244
/// <returns></returns>
245
public abstract class Entry
250
/// default public constructor, needed for generics. You should not use that one, but use the
251
/// CreateInstance method for the entry you want to create
253
/// <returns></returns>
258
/// <summary>override for ToString, returns the Entries Title</summary>
259
public override string ToString()
265
/// needs to be subclassed to ensure the creation of the corrent AtomEntry based
268
protected abstract void EnsureInnerObject();
271
/// the original AtomEntry object that this object is standing in for
273
/// <returns></returns>
274
public AtomEntry AtomEntry
287
/// returns the Id of an entry
294
return this.e.Id.AbsoluteUri;
299
this.e.Id = new AtomId(value);
304
/// returns the value of the self uri as a string
306
/// <returns></returns>
312
if (this.e.SelfUri != null)
314
return this.e.SelfUri.ToString();
322
/// the title of the Entry.
324
/// <returns></returns>
325
public virtual string Title
330
return this.e.Title.Text;
335
this.e.Title.Text = value;
340
/// returns the appControl sublement
342
public AppControl AppControl
347
return this.e.AppControl;
352
this.e.AppControl = value;
358
/// returns the appControl sublement
365
return this.e.IsDraft;
370
/// returns true, if the entry has an edit link
377
return this.e.EditUri == null;
383
/// returns the first author name in the atom.entry.authors collection
385
/// <returns></returns>
391
if (this.e.Authors.Count > 0 && this.e.Authors[0] != null)
393
return this.e.Authors[0].Name;
401
if (this.e.Authors.Count == 0)
403
p = new AtomPerson(AtomPersonType.Author);
404
this.e.Authors.Add(p);
408
p = this.e.Authors[0];
415
/// returns the string representation of the atom.content element
417
/// <returns></returns>
418
public string Content
423
return this.e.Content.Content;
428
this.e.Content.Content = value;
433
/// returns the string representation of the atom.Summary element
435
/// <returns></returns>
436
public string Summary
441
return this.e.Summary.Text;
446
this.e.Summary.Text = value;
451
/// just a thin layer on top of the existing updated of the
452
/// underlying atomentry
454
public DateTime Updated
459
return this.e.Updated;
464
this.e.Updated = value;
470
/// this returns the batch data for the inner atom object
472
/// <returns></returns>
473
public GDataBatchEntryData BatchData
478
return this.e.BatchData;
483
this.e.BatchData = value;
491
/// Base requestsettings class. Takes credentials, applicationsname
492
/// and supports pagesizes and autopaging. This class is used to initialize a
493
/// <seealso cref="FeedRequest<T>"/> object.
495
/// <returns></returns>
496
public class RequestSettings
498
private string applicationName;
499
private GDataCredentials credentials;
500
private string authSubToken;
501
private int pageSize = -1;
502
private int max = -1;
503
private bool autoPage;
504
private int timeout = -1;
507
/// an unauthenticated use case
509
/// <param name="applicationName"></param>
510
/// <returns></returns>
511
public RequestSettings(string applicationName)
513
this.applicationName = applicationName;
517
/// a constructor for client login use cases
519
/// <param name="applicationName">The name of the application</param>
520
/// <param name="userName">the user name</param>
521
/// <param name="passWord">the password</param>
522
/// <returns></returns>
523
public RequestSettings(string applicationName, string userName, string passWord)
525
this.applicationName = applicationName;
526
this.credentials = new GDataCredentials(userName, passWord);
530
/// a constructor for client login use cases
532
/// <param name="applicationName">The name of the application</param>
533
/// <param name="credentials">the user credentials</param>
534
/// <returns></returns>
535
public RequestSettings(string applicationName, GDataCredentials credentials)
537
this.applicationName = applicationName;
538
this.credentials = credentials;
543
/// a constructor for a web application authentication scenario
545
/// <param name="applicationName"></param>
546
/// <param name="authSubToken"></param>
547
/// <returns></returns>
548
public RequestSettings(string applicationName, string authSubToken)
550
this.applicationName = applicationName;
551
this.authSubToken = authSubToken;
556
/// returns the Credentials in case of a client login scenario
558
/// <returns></returns>
559
public GDataCredentials Credentials
563
return this.credentials;
568
/// returns the authsub token to use for a webapplication scenario
570
/// <returns></returns>
571
public string AuthSubToken
575
return this.authSubToken;
580
/// returns the application name
582
/// <returns></returns>
583
public string Application
587
return this.applicationName;
592
/// the pagesize specifies how many entries should be retrieved per call. If not set,
593
/// the server default will be used. Set it either to -1 (for default) or any value > 0
594
/// to set the pagesize to something the server should honor. Note, that this set's the
595
/// max-results parameter on the query, and the server is free to ignore that and give you less
596
/// entries than you have requested.
599
/// The following code illustrates a possible use of
600
/// the <c>PageSize</c> property:
602
/// YouTubeRequestSettings settings = new YouTubeRequestSettings("yourApp", "yourClient", "yourKey", "username", "pwd");
603
/// settings.PageSize = 50;
606
/// <returns></returns>
611
return this.pageSize;
615
this.pageSize = value;
620
/// AutoPaging specifies if a feed iterator should return to the server to fetch more data
621
/// automatically. If set to false, a loop over feed.Entries will stop when the currently
622
/// fetched set of data reaches it's end. This is false by default. <seealso cref="RequestSettings.Maximum"/>
626
/// The following code illustrates a possible use of
627
/// the <c>AutoPaging</c> property:
629
/// YouTubeRequestSettings settings = new YouTubeRequestSettings("yourApp", "yourClient", "yourKey", "username", "pwd");
630
/// settings.AutoPaging = true;
633
/// <returns></returns>
634
public bool AutoPaging
638
return this.autoPage;
642
this.autoPage = value;
647
/// the Maximum specifies how many entries should be retrieved in total. This works together with
648
/// <seealso cref="RequestSettings.AutoPaging"/>. If set, AutoPaging of a feed will stop when the
649
/// specified amount of entries was iterated over. If Maximum is smaller than PageSize (<seealso cref="RequestSettings.PageSize"/>),
650
/// an exception is thrown. The default is -1 (ignored).
653
/// The following code illustrates a possible use of
654
/// the <c>Maximum</c> property:
656
/// YouTubeRequestSettings settings = new YouTubeRequestSettings("yourApp", "yourClient", "yourKey", "username", "pwd");
657
/// settings.PageSize = 50;
658
/// settings.AutoPaging = true;
659
/// settings.Maximum = 2000;
662
/// <returns></returns>
671
if (value < this.PageSize)
673
throw new ArgumentException("Maximum must be greater or equal to PageSize");
680
/// <summary>get's and set's the Timeout property used for the created
681
/// HTTPRequestObject in milliseconds. if you set it to -1 it will stick
682
/// with the default of the HTPPRequestObject. From MSDN:
683
/// The number of milliseconds to wait before the request times out.
684
/// The default is 100,000 milliseconds (100 seconds).</summary>
686
/// The following code illustrates a possible use of
687
/// the <c>Timeout</c> property:
689
/// YouTubeRequestSettings settings = new YouTubeRequestSettings("yourApp", "yourClient", "yourKey", "username", "pwd");
690
/// settings.Timout = 10000000;
693
/// <returns></returns>
702
this.timeout = value;
709
/// the enum used for Get of T requests
711
public enum FeedRequestType
714
/// returns the next feed chunk if there is more data
718
/// returns the previous feed chunk if there is data before
722
/// refreshes the actual feed chunk by going to the server and retrieving it again
729
/// base class for Request objects.
731
/// <returns></returns>
732
public abstract class FeedRequest<T> where T : Service
734
private RequestSettings settings;
735
private T atomService;
741
/// default constructor based on a RequestSettings object
743
/// <param name="settings"></param>
744
public FeedRequest(RequestSettings settings)
746
this.settings = settings;
750
/// prepares the created service based on the settings
752
protected void PrepareService()
754
PrepareService(this.atomService);
758
/// prepares the passed in service by setting the authentication credentials and the timeout settings
760
/// <param name="s"></param>
761
protected void PrepareService(Service s)
763
if (settings.Credentials != null)
765
s.Credentials = settings.Credentials;
767
#if WindowsCE || PocketPC
769
if (settings.AuthSubToken != null)
771
GAuthSubRequestFactory authFactory = new GAuthSubRequestFactory(s.ServiceIdentifier, settings.Application);
772
authFactory.UserAgent = authFactory.UserAgent + "--IEnumerable";
773
authFactory.Token = settings.AuthSubToken;
774
s.RequestFactory = authFactory;
778
GDataGAuthRequestFactory authFactory = s.RequestFactory as GDataGAuthRequestFactory;
779
if (authFactory != null)
781
authFactory.UserAgent = authFactory.UserAgent + "--IEnumerable";
785
if (settings.Timeout != -1)
787
GDataRequestFactory f = s.RequestFactory as GDataRequestFactory;
790
f.Timeout = settings.Timeout;
797
/// creates a query object and set's it up based on the settings object.
799
/// <typeparam name="Y"></typeparam>
800
/// <param name="uri"></param>
801
/// <returns></returns>
802
protected Y PrepareQuery<Y>(string uri) where Y: FeedQuery, new()
805
query.BaseAddress = uri;
812
/// prepares the passed in query objects properties based on the settings
814
/// <param name="q"></param>
815
protected void PrepareQuery(FeedQuery q)
817
if (this.settings.PageSize != -1)
819
q.NumberToRetrieve = this.settings.PageSize;
824
/// creates a feed of Y object based on the query and the settings
826
/// <typeparam name="Y"></typeparam>
827
/// <param name="q"></param>
828
/// <returns></returns>
829
protected virtual Feed<Y> PrepareFeed<Y>(FeedQuery q) where Y : Entry, new()
831
// AtomFeed feed = this.atomService.Query(q);
832
// Feed<Y> f = new Feed<Y>(feed);
833
Feed<Y> f = new Feed<Y>(this.atomService, q);
834
f.AutoPaging = this.settings.AutoPaging;
835
f.Maximum = this.settings.Maximum;
840
/// gets a feed object of type T
842
/// <typeparam name="Y"></typeparam>
843
/// <param name="q"></param>
844
/// <returns></returns>
845
public Feed<Y> Get<Y>(FeedQuery q) where Y: Entry, new()
847
return PrepareFeed<Y>(q);
851
/// gets a feed object of type T
853
/// <typeparam name="Y"></typeparam>
854
/// <param name="uri">The Uri to retrieve</param>
855
/// <returns></returns>
856
public Feed<Y> Get<Y>(Uri uri) where Y : Entry, new()
858
FeedQuery q = new FeedQuery(uri.AbsoluteUri);
859
return PrepareFeed<Y>(q);
864
/// returns a new feed based on the operation passed in. This is useful if you either do not use
865
/// autopaging, or want to move to previous parts of the feed, or get a refresh of the current feed
868
/// The following code illustrates a possible use of
869
/// the <c>Get</c> method:
871
/// YouTubeRequestSettings settings = new YouTubeRequestSettings("yourApp", "yourClient", "yourKey", "username", "pwd");
872
/// YouTubeRequest f = new YouTubeRequest(settings);
873
/// Feed<Playlist> feed = f.GetPlaylistsFeed(null);
874
/// Feed<Playlist> next = f.Get<Playlist>(feed, FeedRequestType.Next);
877
/// <param name="feed">the original feed</param>
878
/// <param name="operation">an requesttype to indicate what to retrieve</param>
879
/// <returns></returns>
880
public Feed<Y> Get<Y>(Feed<Y> feed, FeedRequestType operation) where Y: Entry, new()
887
throw new ArgumentNullException("feed was null");
890
if (feed.AtomFeed == null)
892
throw new ArgumentNullException("feed.AtomFeed was null");
897
case FeedRequestType.Next:
898
spec = feed.AtomFeed.NextChunk;
900
case FeedRequestType.Prev:
901
spec = feed.AtomFeed.PrevChunk;
903
case FeedRequestType.Refresh:
904
spec = feed.AtomFeed.Self;
907
if (String.IsNullOrEmpty(spec) == false)
909
FeedQuery q = new FeedQuery(spec);
910
if (operation == FeedRequestType.Refresh)
912
ISupportsEtag ise = feed.AtomFeed as ISupportsEtag;
913
if (ise != null && ise.Etag != null)
918
f = PrepareFeed<Y>(q);
925
/// performs a batch operation.
927
/// <param name="feed">the original feed, used to find the batch endpoing </param>
928
/// <param name="entries">List of entries of type Y, that are to be batched</param>
929
/// <returns></returns>
930
public Feed<Y> Batch<Y>(List<Y> entries, Feed<Y> feed) where Y : Entry, new()
932
return this.Batch(entries, feed, GDataBatchOperationType.Default);
938
/// performs a batch operation.
940
/// <param name="feed">the original feed, used to find the batch endpoing </param>
941
/// <param name="entries">List of entries of type Y, that are to be batched</param>
942
/// <param name="defaultOperation">indicates the default batch operationtype</param>
943
/// <returns></returns>
944
public Feed<Y> Batch<Y>(List<Y> entries, Feed<Y> feed, GDataBatchOperationType defaultOperation) where Y : Entry, new()
947
feed.AtomFeed == null)
949
throw new ArgumentNullException("Invalid feed passed in");
952
if (feed.AtomFeed.Batch == null)
954
throw new ArgumentException("Feed has no valid batch endpoint");
956
return this.Batch(entries, new Uri(feed.AtomFeed.Batch), defaultOperation);
962
/// performs a batch operation.
964
/// <param name="batchUri">the batch endpoint of the service</param>
965
/// <param name="entries">List of entries of type Y, that are to be batched</param>
966
/// <returns></returns>
967
public Feed<Y> Batch<Y>(List<Y> entries, Uri batchUri, GDataBatchOperationType defaultOperation) where Y: Entry, new()
969
if (entries.Count > 0)
971
AtomFeed batchFeed = new AtomFeed(batchUri, null);
972
batchFeed.BatchData = new GDataBatchFeedData();
973
batchFeed.BatchData.Type = defaultOperation;
974
foreach (Y e in entries)
976
batchFeed.Entries.Add(e.AtomEntry);
978
AtomFeed resultFeed = this.Service.Batch(batchFeed, batchUri);
979
Feed<Y> f = new Feed<Y>(resultFeed);
988
/// returns the service instance that is used
994
return this.atomService;
998
this.atomService = value;
1004
/// returns a refreshed version of the entry you passed in, by going back to the server and
1005
/// requesting this resource again
1008
/// The following code illustrates a possible use of
1009
/// the <c>Get</c> method:
1011
/// YouTubeRequestSettings settings = new YouTubeRequestSettings("yourApp", "yourClient", "yourKey", "username", "pwd");
1012
/// YouTubeRequest f = new YouTubeRequest(settings);
1013
/// Feed<Playlist> feed = f.GetPlaylistsFeed(null);
1014
/// Feed<Playlist> next = f.Get<Playlist>(feed, FeedRequestType.Next);
1017
/// <param name="entry">the entry to get again</param>
1018
/// <returns></returns>
1019
public Y Retrieve<Y>(Y entry) where Y: Entry, new()
1023
throw new ArgumentNullException("entry was null");
1026
if (entry.AtomEntry == null)
1028
throw new ArgumentNullException("entry.AtomEntry was null");
1031
string spec =entry.AtomEntry.SelfUri.ToString();
1033
if (String.IsNullOrEmpty(spec) == false)
1035
FeedQuery q = new FeedQuery(spec);
1036
ISupportsEtag ise = entry.AtomEntry as ISupportsEtag;
1037
if (ise != null && ise.Etag != null)
1041
return Retrieve<Y>(q);
1047
/// returns a the entry the Uri pointed to
1050
/// <param name="entryUri">the Uri of the entry</param>
1051
/// <returns></returns>
1052
public Y Retrieve<Y>(Uri entryUri) where Y : Entry, new()
1054
string spec = entryUri.AbsoluteUri;
1055
if (String.IsNullOrEmpty(spec) == false)
1057
FeedQuery q = new FeedQuery(spec);
1058
return Retrieve<Y>(q);
1064
/// returns a the entry the Uri pointed to
1067
/// <param name="entryUri">the Uri of the entry</param>
1068
/// <returns></returns>
1069
public Y Retrieve<Y>(FeedQuery query) where Y : Entry, new()
1073
f = PrepareFeed<Y>(query);
1074
// this should be a feed of one...
1075
foreach (Y y in f.Entries)
1084
/// sends the data back to the server.
1086
/// <returns>the reflected entry from the server if any given</returns>
1087
public Y Update<Y>(Y entry) where Y: Entry, new()
1090
throw new ArgumentNullException("Entry was null");
1092
if (entry.AtomEntry == null)
1093
throw new ArgumentNullException("Entry.AtomEntry was null");
1096
AtomEntry ae = this.Service.Update(entry.AtomEntry);
1107
/// deletes the Entry from the Server
1109
public void Delete<Y>(Y entry) where Y : Entry, new()
1112
throw new ArgumentNullException("Entry was null");
1114
if (entry.AtomEntry == null)
1115
throw new ArgumentNullException("Entry.AtomEntry was null");
1117
entry.AtomEntry.Delete();
1121
/// takes the given Entry and inserts its into the server
1123
/// <returns>the reflected entry from the server if any given</returns>
1124
public Y Insert<Y>(Uri address, Y entry) where Y : Entry, new()
1127
throw new ArgumentNullException("Entry was null");
1129
if (entry.AtomEntry == null)
1130
throw new ArgumentNullException("Entry.AtomEntry was null");
1132
if (address == null)
1133
throw new ArgumentNullException("Entry was null");
1136
AtomEntry ae = this.Service.Insert(address, entry.AtomEntry);
1148
/// takes the given Entry and inserts its into the server
1150
/// <returns>the reflected entry from the server if any given</returns>
1151
public Y Insert<Y>(Feed<Y> feed, Y entry) where Y : Entry, new()
1154
throw new ArgumentNullException("Entry was null");
1156
if (entry.AtomEntry == null)
1157
throw new ArgumentNullException("Entry.AtomEntry was null");
1160
throw new ArgumentNullException("Feed was null");
1163
AtomEntry ae = this.Service.Insert(feed.AtomFeed, entry.AtomEntry);
1173
/// the Settings property returns the RequestSettings object that was used to construct this FeedRequest.
1174
/// It can be used to alter properties like AutoPaging etc, inbetween Feed creations.
1177
/// The following code illustrates a possible use of
1178
/// the <c>Settings</c> property:
1180
/// YouTubeRequestSettings settings = new YouTubeRequestSettings("NETUnittests", this.ytClient, this.ytDevKey, this.ytUser, this.ytPwd);
1181
/// YouTubeRequest f = new YouTubeRequest(settings);
1182
/// Feed<Video> feed = f.GetStandardFeed(YouTubeQuery.MostPopular);
1183
/// foreach (Video v in feed.Entries)
1185
/// f.Settings.PageSize = 50;
1186
/// f.Settings.AutoPaging = true;
1187
/// Feed<Comment> list = f.GetComments(v);
1188
/// foreach (Comment c in list.Entries)
1190
/// Assert.IsTrue(v.AtomEntry != null);
1191
/// Assert.IsTrue(v.Title != null);
1196
/// <returns></returns>
1197
public RequestSettings Settings
1201
return this.settings;