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.
16
* Oct 13 2008 Joe Feser joseph.feser@gmail.com
20
#region Using directives
28
using System.Threading;
29
using System.ComponentModel;
30
using System.Collections.Specialized;
36
/////////////////////////////////////////////////////////////////////
37
// <summary>contains Service, the base interface that
38
// allows to query a service for different feeds
40
////////////////////////////////////////////////////////////////////
41
namespace Google.GData.Client
45
/// EventArgument class for service level events during parsing
47
public class ServiceEventArgs : EventArgs
49
private AtomFeed feedObject;
50
private IService service;
54
/// constructor. Takes the URI and the service this event applies to
56
/// <param name="uri">URI currently executed</param>
57
/// <param name="service">service object doing the execution</param>
58
public ServiceEventArgs(Uri uri, IService service)
60
this.service = service;
64
/// <summary>the feed to be created. If this is NULL, a service
65
/// will create a DEFAULT atomfeed</summary>
66
/// <returns> </returns>
67
//////////////////////////////////////////////////////////////////////
70
get {return this.feedObject;}
71
set {this.feedObject = value;}
73
////////////////////////////////////////////////////////////////////////
74
//////////////////////////////////////////////////////////////////////
75
/// <summary>the service to be used for the feed to be created. </summary>
76
/// <returns> </returns>
77
//////////////////////////////////////////////////////////////////////
78
public IService Service
80
get {return this.service;}
82
////////////////////////////////////////////////////////////////////////
83
//////////////////////////////////////////////////////////////////////
84
/// <summary>the Uri to be used</summary>
85
/// <returns> </returns>
86
//////////////////////////////////////////////////////////////////////
89
get {return this.uri;}
91
////////////////////////////////////////////////////////////////////////
96
/// <summary>Delegate declaration for the feed creation in a service</summary>
97
public delegate void ServiceEventHandler(object sender, ServiceEventArgs e);
101
//////////////////////////////////////////////////////////////////////
102
/// <summary>base Service implementation
104
//////////////////////////////////////////////////////////////////////
105
public partial class Service : IService, IVersionAware
107
/// <summary>holds the credential information</summary>
108
private GDataCredentials credentials;
109
/// <summary>the GDatarequest to use</summary>
110
private IGDataRequestFactory factory;
111
/// <summary>holds the hooks for the eventing in the feedparser</summary>
112
public event FeedParserEventHandler NewAtomEntry;
113
/// <summary>eventhandler, when the parser finds a new extension element-> mirrored from underlying parser</summary>
114
public event ExtensionElementEventHandler NewExtensionElement;
115
/// <summary>eventhandler, when the service needs to create a new feed</summary>
116
public event ServiceEventHandler NewFeed;
118
private string serviceID;
121
//////////////////////////////////////////////////////////////////////
122
/// <summary>default constructor, sets the default GDataRequest</summary>
123
//////////////////////////////////////////////////////////////////////
126
this.RequestFactory = new GDataRequestFactory(this.GetType().Name);
128
InitVersionInformation();
130
/////////////////////////////////////////////////////////////////////////////
133
//////////////////////////////////////////////////////////////////////
134
/// <summary>default constructor, sets the default GDataRequest</summary>
135
//////////////////////////////////////////////////////////////////////
136
public Service(string applicationName)
138
this.RequestFactory = new GDataRequestFactory(applicationName == null ?
139
this.GetType().Name :
142
InitVersionInformation();
144
/////////////////////////////////////////////////////////////////////////////
147
//////////////////////////////////////////////////////////////////////
148
/// <summary>this will trigger the creation of an authenticating service</summary>
149
//////////////////////////////////////////////////////////////////////
150
public Service(string service, string applicationName)
152
this.RequestFactory = new GDataGAuthRequestFactory(service, applicationName);
153
this.serviceID = service;
155
InitVersionInformation();
157
/////////////////////////////////////////////////////////////////////////////
161
/// this returns the string that the services uses to identify the google service to use
162
/// when authentication with Google is required. Examples are "cl" for calendar, e.g.
164
/// <returns></returns>
165
public string ServiceIdentifier
169
return this.serviceID;
173
private VersionInformation versionInfo = new VersionInformation();
175
/// returns the major protocol version number this element
176
/// is working against.
178
/// <returns></returns>
179
public int ProtocolMajor
183
return this.versionInfo.ProtocolMajor;
187
this.versionInfo.ProtocolMajor = value;
188
PropagateVersionInfo();
193
/// returns the minor protocol version number this element
194
/// is working against.
196
/// <returns></returns>
197
public int ProtocolMinor
201
return this.versionInfo.ProtocolMinor;
205
this.versionInfo.ProtocolMinor = value;
206
PropagateVersionInfo();
211
/// by default all services now use version 2 for the protocol.
212
/// this needs to be overridden by a service to specify otherwise.
214
/// <returns></returns>
215
protected virtual void InitVersionInformation()
219
//////////////////////////////////////////////////////////////////////
220
/// <summary>accessor method public IGDataRequest Request</summary>
221
/// <returns> </returns>
222
//////////////////////////////////////////////////////////////////////
223
public IGDataRequestFactory RequestFactory
225
get { return this.factory; }
226
set { this.factory = value; OnRequestFactoryChanged(); }
230
/// notifier if someone changes the requestfactory of the service.
231
/// This will cause the service to set the versionnumber on the
232
/// request factory to it's own
234
public virtual void OnRequestFactoryChanged()
236
PropagateVersionInfo();
239
private void PropagateVersionInfo()
241
IVersionAware v = this.factory as IVersionAware;
244
v.ProtocolMajor = this.ProtocolMajor;
245
v.ProtocolMinor = this.ProtocolMinor;
250
//////////////////////////////////////////////////////////////////////
251
/// <summary>accessor method public ICredentials Credentials</summary>
252
/// <returns> </returns>
253
//////////////////////////////////////////////////////////////////////
254
public GDataCredentials Credentials
256
get {return this.credentials;}
259
this.credentials = value;
260
// if we get new credentials, make sure we invalidate the old authtoken
261
SetAuthenticationToken(value == null ? null : value.ClientToken);
264
/////////////////////////////////////////////////////////////////////////////
268
/// if the service is using a Google Request Factory it will use that
269
/// assuming credentials are set to retrieve the authentication token
270
/// for those credentials
272
/// <returns>string</returns>
273
public string QueryAuthenticationToken()
275
if (this.Credentials != null)
277
GDataGAuthRequestFactory factory = this.factory as GDataGAuthRequestFactory;
280
return factory.QueryAuthToken(this.Credentials);
287
/// if the service is using a Google Request Factory it will set the passed
288
/// in token to the factory. NET CF does not support authsubtokens here
290
/// <returns>string</returns>
291
public void SetAuthenticationToken(string token)
293
GDataGAuthRequestFactory factory = this.factory as GDataGAuthRequestFactory;
296
factory.GAuthToken = token;
298
#if WindowsCE || PocketPC
302
GAuthSubRequestFactory f = this.factory as GAuthSubRequestFactory;
312
/// Sets the credentials of the user to authenticate requests
315
/// <param name="username"></param>
316
/// <param name="password"></param>
317
public void setUserCredentials(String username, String password)
319
this.Credentials = new GDataCredentials(username, password);
325
//////////////////////////////////////////////////////////////////////
326
/// <summary>the basic interface. Take a URI and just get it</summary>
327
/// <param name="queryUri">the URI to execute</param>
328
/// <returns> a webresponse object</returns>
329
//////////////////////////////////////////////////////////////////////
330
public Stream Query(Uri queryUri)
332
return Query(queryUri, DateTime.MinValue);
335
//////////////////////////////////////////////////////////////////////
336
/// <summary>the basic interface. Take a URI and just get it</summary>
337
/// <param name="queryUri">the URI to execute</param>
338
/// <param name="ifModifiedSince">used to set a precondition date that
339
/// indicates the feed should be returned only if it has been modified
340
/// after the specified date. A value of DateTime.MinValue indicates no
341
/// precondition.</param>
342
/// <returns> a webresponse object</returns>
343
//////////////////////////////////////////////////////////////////////
344
public Stream Query(Uri queryUri, DateTime ifModifiedSince)
347
return this.Query(queryUri, ifModifiedSince, null, out l);
349
/////////////////////////////////////////////////////////////////////////////
352
//////////////////////////////////////////////////////////////////////
353
/// <summary>the basic interface. Take a URI and just get it</summary>
354
/// <param name="queryUri">the URI to execute</param>
355
/// <param name="etag">used to set a precondition etag that
356
/// indicates the feed should be returned only if it has been modified </param>
357
/// <returns> a webresponse object</returns>
358
//////////////////////////////////////////////////////////////////////
359
public Stream Query(Uri queryUri, string etag)
362
return this.Query(queryUri, DateTime.MinValue, etag, out l);
364
/////////////////////////////////////////////////////////////////////////////
366
//////////////////////////////////////////////////////////////////////
367
/// <summary>the basic interface. Take a URI and just get it</summary>
368
/// <param name="queryUri">the URI to execute</param>
369
/// <param name="ifModifiedSince">used to set a precondition date that
370
/// indicates the feed should be returned only if it has been modified
371
/// after the specified date. A value of DateTime.MinValue indicates no
372
/// precondition.</param>
373
/// <param name="etag">used to set a precondition etag that
374
/// indicates the feed should be returned only if it has been modified </param>
375
/// <param name="contentLength">returns the content length of the response</param>
376
/// <returns> a webresponse object</returns>
377
//////////////////////////////////////////////////////////////////////
378
private Stream Query(Uri queryUri, DateTime ifModifiedSince, string etag, out long contentLength)
380
Tracing.TraceCall("Enter");
381
if (queryUri == null)
383
throw new System.ArgumentNullException("queryUri");
388
IGDataRequest request = this.RequestFactory.CreateRequest(GDataRequestType.Query, queryUri);
389
request.Credentials = this.Credentials;
390
request.IfModifiedSince = ifModifiedSince;
394
ISupportsEtag ise = request as ISupportsEtag;
407
// Prevent connection leaks
408
if (request.GetResponseStream() != null)
409
request.GetResponseStream().Close();
414
// return the response
415
GDataGAuthRequest gr = request as GDataGAuthRequest;
418
contentLength = gr.ContentLength;
421
Tracing.TraceCall("Exit");
422
return new GDataReturnStream(request);
424
/////////////////////////////////////////////////////////////////////////////
428
/// Returns a single Atom entry based upon its unique URI.
430
/// <param name="entryUri">The URI of the Atom entry.</param>
431
/// <returns>AtomEntry representing the entry.</returns>
432
public AtomEntry Get(string entryUri)
434
FeedQuery query = new FeedQuery(entryUri);
435
AtomFeed resultFeed = Query(query);
436
return resultFeed.Entries[0];
441
//////////////////////////////////////////////////////////////////////
442
/// <summary>executes the query and returns an AtomFeed object tree</summary>
443
/// <param name="feedQuery">the query parameters as a FeedQuery object </param>
444
/// <returns>AtomFeed object tree</returns>
445
//////////////////////////////////////////////////////////////////////
446
public AtomFeed Query(FeedQuery feedQuery)
448
AtomFeed feed = null;
449
Tracing.TraceCall("Enter");
451
if (feedQuery == null)
453
throw new System.ArgumentNullException("feedQuery", "The query argument MUST not be null");
455
// Create a new request to the Uri in the query object...
456
Uri targetUri = null;
460
targetUri = feedQuery.Uri;
463
catch (System.UriFormatException)
465
throw new System.ArgumentException("The query argument MUST contain a valid Uri", "feedQuery");
468
Tracing.TraceInfo("Service:Query - about to query");
470
Stream responseStream = null;
472
if (feedQuery.Etag != null)
474
responseStream = Query(targetUri, feedQuery.Etag);
478
responseStream = Query(targetUri, feedQuery.ModifiedSince);
481
Tracing.TraceInfo("Service:Query - query done");
482
if (responseStream != null)
484
feed = CreateAndParseFeed(responseStream, feedQuery.Uri);
486
Tracing.TraceCall("Exit");
490
/////////////////////////////////////////////////////////////////////////////
493
//////////////////////////////////////////////////////////////////////
494
/// <summary>executes the query and returns an AtomFeed object tree</summary>
495
/// <param name="feedQuery">the query parameters as a FeedQuery object </param>
496
/// <param name="ifModifiedSince">used to set a precondition date that
497
/// indicates the feed should be returned only if it has been modified
498
/// after the specified date. A value of null indicates no
499
/// precondition.</param>
500
/// <returns>AtomFeed object tree</returns>
501
//////////////////////////////////////////////////////////////////////
502
[Obsolete("FeedQuery has a modifiedSince property, use that instead")]
503
public AtomFeed Query(FeedQuery feedQuery, DateTime ifModifiedSince)
505
feedQuery.ModifiedSince = ifModifiedSince;
506
return Query(feedQuery);
508
/////////////////////////////////////////////////////////////////////////////
514
//////////////////////////////////////////////////////////////////////
515
/// <summary>object QueryOpenSearchRssDescription()</summary>
516
/// <param name="serviceUri">the service to ask for an OpenSearchRss Description</param>
517
/// <returns> a webresponse object</returns>
518
//////////////////////////////////////////////////////////////////////
519
public Stream QueryOpenSearchRssDescription(Uri serviceUri)
521
if (serviceUri == null)
523
throw new System.ArgumentNullException("serviceUri");
525
IGDataRequest request = this.RequestFactory.CreateRequest(GDataRequestType.Query, serviceUri);
526
request.Credentials = this.Credentials;
528
// return the response
529
return request.GetResponseStream();
531
/////////////////////////////////////////////////////////////////////////////
535
//////////////////////////////////////////////////////////////////////
536
/// <summary>WebResponse Update(Uri updateUri, Stream entryStream, ICredentials credentials)</summary>
537
/// <param name="entry">the old entry to update</param>
538
/// <returns> the new Entry, as returned from the server</returns>
539
//////////////////////////////////////////////////////////////////////
540
public AtomEntry Update(AtomEntry entry)
542
return this.Update(entry, null);
544
/////////////////////////////////////////////////////////////////////////////
549
//////////////////////////////////////////////////////////////////////
550
/// <summary>templated type safe verion of the interface</summary>
551
/// <param name="entry">the old entry to update</param>
552
/// <returns> the new Entry, as returned from the server</returns>
553
//////////////////////////////////////////////////////////////////////
554
public TEntry Update<TEntry>(TEntry entry) where TEntry : AtomEntry
556
return this.Update(entry, null) as TEntry;
558
/////////////////////////////////////////////////////////////////////////////
561
//////////////////////////////////////////////////////////////////////
562
/// <summary>WebResponse Update(Uri updateUri, Stream entryStream, ICredentials credentials)</summary>
563
/// <param name="entry">the old entry to update</param>
564
/// <param name="data">the async data block used</param>
565
/// <returns> the new Entry, as returned from the server</returns>
566
//////////////////////////////////////////////////////////////////////
567
private AtomEntry Update(AtomEntry entry, AsyncSendData data)
569
Tracing.Assert(entry != null, "entry should not be null");
572
throw new ArgumentNullException("entry");
575
if (entry.ReadOnly == true)
577
throw new GDataRequestException("Can not update a read-only entry");
581
Uri target = new Uri(entry.EditUri.ToString());
583
Stream returnStream = EntrySend(target, entry, GDataRequestType.Update, data);
584
return CreateAndParseEntry(returnStream, target);
586
/////////////////////////////////////////////////////////////////////////////
590
//////////////////////////////////////////////////////////////////////
591
/// <summary>public WebResponse Insert(Uri insertUri, Stream entryStream, ICredentials credentials)</summary>
592
/// <param name="feed">the feed this entry should be inserted into</param>
593
/// <param name="entry">the entry to be inserted</param>
594
/// <returns> the inserted entry</returns>
595
//////////////////////////////////////////////////////////////////////
596
AtomEntry IService.Insert(AtomFeed feed, AtomEntry entry)
599
Tracing.Assert(feed != null, "feed should not be null");
602
throw new ArgumentNullException("feed");
604
Tracing.Assert(entry != null, "entry should not be null");
607
throw new ArgumentNullException("entry");
610
if (feed.ReadOnly == true)
612
throw new GDataRequestException("Can not update a read-only feed");
615
Tracing.TraceMsg("Post URI is: " + feed.Post);
616
Uri target = new Uri(feed.Post);
617
return Insert(target, entry);
619
/////////////////////////////////////////////////////////////////////////////
624
//////////////////////////////////////////////////////////////////////
626
/// templated type safe version of Insert
628
/// <typeparam name="TEntry"></typeparam>
629
/// <param name="feed"></param>
630
/// <param name="entry"></param>
631
/// <returns> the new Entry, as returned from the server</returns>
632
public TEntry Insert<TEntry>(AtomFeed feed, TEntry entry) where TEntry : AtomEntry
634
IService s = this as IService;
635
return s.Insert(feed, entry) as TEntry;
637
/////////////////////////////////////////////////////////////////////////////
639
//////////////////////////////////////////////////////////////////////
640
/// <summary>templated type safe verion of the interface</summary>
641
/// <typeparam name="TEntry"></typeparam>
642
/// <param name="feedUri"></param>
643
/// <param name="entry">the old entry to update</param>
644
/// <returns> the new Entry, as returned from the server</returns>
645
public TEntry Insert<TEntry>(Uri feedUri, TEntry entry) where TEntry : AtomEntry
647
return this.Insert(feedUri, entry, null) as TEntry;
649
/////////////////////////////////////////////////////////////////////////////
652
/// internal Insert version to avoid recursion in the template versions
654
/// <param name="feedUri"></param>
655
/// <param name="newEntry"></param>
656
/// <returns></returns>
657
protected AtomEntry internalInsert(Uri feedUri, AtomEntry newEntry)
659
return this.Insert(feedUri, newEntry, null);
664
//////////////////////////////////////////////////////////////////////
665
/// <summary>public WebResponse Insert(Uri insertUri, Stream entryStream, ICredentials credentials)</summary>
666
/// <param name="feedUri">the uri for the feed this entry should be inserted into</param>
667
/// <param name="newEntry">the entry to be inserted</param>
668
/// <param name="data">the data used for an async request</param>
669
/// <returns> the inserted entry</returns>
670
//////////////////////////////////////////////////////////////////////
671
private AtomEntry Insert(Uri feedUri, AtomEntry newEntry, AsyncSendData data)
673
Tracing.Assert(feedUri != null, "feedUri should not be null");
676
throw new ArgumentNullException("feedUri");
678
Tracing.Assert(newEntry != null, "newEntry should not be null");
679
if (newEntry == null)
681
throw new ArgumentNullException("newEntry");
683
this.versionInfo.ImprintVersion(newEntry);
685
Stream returnStream = EntrySend(feedUri, newEntry, GDataRequestType.Insert, data);
686
return CreateAndParseEntry(returnStream, feedUri);
688
/////////////////////////////////////////////////////////////////////////////
693
/// simple update for media resources
695
/// <param name="uriTarget"></param>
696
/// <param name="input">the stream to send</param>
697
/// <param name="contentType"></param>
698
/// <param name="slugHeader">the value for the slug header, indicating filenaming</param>
699
/// <returns>AtomEntry</returns>
700
public AtomEntry Update(Uri uriTarget, Stream input, string contentType, string slugHeader)
702
Stream returnStream = StreamSend(uriTarget, input, GDataRequestType.Update, contentType, slugHeader);
703
return CreateAndParseEntry(returnStream, uriTarget);
707
/// Simple insert for media resources
709
/// <param name="uriTarget"></param>
710
/// <param name="input"></param>
711
/// <param name="contentType"></param>
712
/// <param name="slugHeader">the value for the slug header, indicating filenaming</param>
713
/// <returns>AtomEntry</returns>
714
public AtomEntry Insert(Uri uriTarget, Stream input, string contentType, string slugHeader)
716
Stream returnStream = StreamSend(uriTarget, input, GDataRequestType.Insert, contentType, slugHeader);
717
return CreateAndParseEntry(returnStream, uriTarget);
721
private AtomFeed CreateAndParseFeed(Stream inputStream, Uri uriToUse)
723
AtomFeed returnFeed = null;
725
if (inputStream != null)
727
returnFeed = CreateFeed(uriToUse);
728
this.versionInfo.ImprintVersion(returnFeed);
731
returnFeed.NewAtomEntry += new FeedParserEventHandler(this.OnParsedNewEntry);
732
returnFeed.NewExtensionElement += new ExtensionElementEventHandler(this.OnNewExtensionElement);
733
returnFeed.Parse(inputStream, AlternativeFormat.Atom);
744
private AtomEntry CreateAndParseEntry(Stream inputStream, Uri uriTarget)
746
AtomFeed returnFeed = CreateAndParseFeed(inputStream, uriTarget);
747
AtomEntry entry=null;
748
// there should be ONE entry echoed back.
749
if (returnFeed != null && returnFeed.Entries.Count > 0)
751
entry = returnFeed.Entries[0];
754
entry.Service = this;
763
//////////////////////////////////////////////////////////////////////
764
/// <summary>Inserts an AtomBase entry against a Uri</summary>
765
/// <param name="feedUri">the uri for the feed this object should be posted against</param>
766
/// <param name="baseEntry">the entry to be inserted</param>
767
/// <param name="type">the type of request to create</param>
768
/// <returns> the response as a stream</returns>
769
//////////////////////////////////////////////////////////////////////
770
public Stream EntrySend(Uri feedUri, AtomEntry baseEntry, GDataRequestType type)
772
return this.EntrySend(feedUri, baseEntry, type, null);
776
//////////////////////////////////////////////////////////////////////
777
/// <summary>Inserts an AtomBase entry against a Uri</summary>
778
/// <param name="feedUri">the uri for the feed this object should be posted against</param>
779
/// <param name="baseEntry">the entry to be inserted</param>
780
/// <param name="type">the type of request to create</param>
781
/// <param name="data">the async data payload</param>
782
/// <returns> the response as a stream</returns>
783
//////////////////////////////////////////////////////////////////////
784
internal virtual Stream EntrySend(Uri feedUri, AtomBase baseEntry, GDataRequestType type, AsyncSendData data)
786
Tracing.Assert(feedUri != null, "feedUri should not be null");
789
throw new ArgumentNullException("feedUri");
791
Tracing.Assert(baseEntry != null, "baseEntry should not be null");
792
if (baseEntry == null)
794
throw new ArgumentNullException("baseEntry");
796
this.versionInfo.ImprintVersion(baseEntry);
798
IGDataRequest request = this.RequestFactory.CreateRequest(type,feedUri);
799
request.Credentials = this.Credentials;
801
ISupportsEtag eTarget = request as ISupportsEtag;
802
ISupportsEtag eSource = baseEntry as ISupportsEtag;
803
if (eTarget != null && eSource != null)
805
eTarget.Etag = eSource.Etag;
809
GDataGAuthRequest gr = request as GDataGAuthRequest;
816
Stream outputStream = request.GetRequestStream();
818
baseEntry.SaveToXml(outputStream);
821
outputStream.Close();
822
return request.GetResponseStream();
828
/// this is a helper function for external utilities. It is not worth
829
/// running the other insert/saves through here, as this would involve
830
/// double buffering/copying of the bytes
832
/// <param name="targetUri"></param>
833
/// <param name="payload"></param>
834
/// <param name="type"></param>
835
/// <returns>Stream</returns>
837
public Stream StringSend(Uri targetUri, String payload, GDataRequestType type)
839
Tracing.Assert(targetUri != null, "targetUri should not be null");
840
if (targetUri == null)
842
throw new ArgumentNullException("targetUri");
844
Tracing.Assert(payload != null, "payload should not be null");
847
throw new ArgumentNullException("payload");
850
IGDataRequest request = this.RequestFactory.CreateRequest(type,targetUri);
851
request.Credentials = this.Credentials;
853
Stream outputStream = request.GetRequestStream();
855
StreamWriter w = new StreamWriter(outputStream);
862
return request.GetResponseStream();
868
/// this is a helper function for to send binary data to a resource
869
/// it is not worth running the other insert/saves through here, as this would involve
870
/// double buffering/copying of the bytes
872
/// <param name="targetUri"></param>
873
/// <param name="inputStream"></param>
874
/// <param name="type"></param>
875
/// <param name="contentType">the contenttype to use in the request, if NULL is passed, factory default is used</param>
876
/// <param name="slugHeader">the slugHeader to use in the request, if NULL is passed, factory default is used</param>
877
/// <returns>Stream</returns>
878
public Stream StreamSend(Uri targetUri,
880
GDataRequestType type,
885
return StreamSend(targetUri, inputStream, type, contentType, slugHeader, null, null);
889
/// this is a helper function for to send binary data to a resource
890
/// it is not worth running the other insert/saves through here, as this would involve
891
/// double buffering/copying of the bytes
893
/// <param name="targetUri"></param>
894
/// <param name="inputStream"></param>
895
/// <param name="type"></param>
896
/// <param name="contentType">the contenttype to use in the request, if NULL is passed, factory default is used</param>
897
/// <param name="slugHeader">the slugHeader to use in the request, if NULL is passed, factory default is used</param>
898
/// <param name="etag">The http etag to pass into the request</param>
899
/// <returns>Stream</returns>
900
public Stream StreamSend(Uri targetUri,
902
GDataRequestType type,
908
return StreamSend(targetUri, inputStream, type, contentType, slugHeader, etag, null);
913
/// this is a helper function for to send binary data to a resource
914
/// it is not worth running the other insert/saves through here, as this would involve
915
/// double buffering/copying of the bytes
917
/// <param name="targetUri"></param>
918
/// <param name="inputStream"></param>
919
/// <param name="type"></param>
920
/// <param name="contentType">the contenttype to use in the request, if NULL is passed, factory default is used</param>
921
/// <param name="slugHeader">the slugHeader to use in the request, if NULL is passed, factory default is used</param>
922
/// <param name="etag">The http etag to pass into the request</param>
923
/// <param name="data">The async data needed for notifications</param>
924
/// <returns>Stream from the server response. You should close this stream explicitly.</returns>
925
private Stream StreamSend(Uri targetUri,
927
GDataRequestType type,
933
Tracing.Assert(targetUri != null, "targetUri should not be null");
934
if (targetUri == null)
936
throw new ArgumentNullException("targetUri");
938
if (inputStream == null)
940
Tracing.Assert(inputStream != null, "payload should not be null");
941
throw new ArgumentNullException("inputStream");
943
if (type != GDataRequestType.Insert && type != GDataRequestType.Update)
945
Tracing.Assert(type != GDataRequestType.Insert && type != GDataRequestType.Update,"type needs to be insert or update");
946
throw new ArgumentNullException("type");
950
IGDataRequest request = this.RequestFactory.CreateRequest(type,targetUri);
951
request.Credentials = this.Credentials;
955
GDataGAuthRequest gr = request as GDataGAuthRequest;
962
// set the contenttype of the request
963
if (contentType != null)
965
GDataRequest r = request as GDataRequest;
968
r.ContentType = contentType;
972
if (slugHeader != null)
974
GDataRequest r = request as GDataRequest;
983
ISupportsEtag ise = request as ISupportsEtag;
990
Stream outputStream = request.GetRequestStream();
992
WriteInputStreamToRequest(inputStream, outputStream);
995
outputStream.Close();
996
return new GDataReturnStream(request);
1000
/// write the current stream to an output stream
1001
/// this is primarily used to write data to the
1004
/// <param name="input"></param>
1005
/// <param name="output"></param>
1006
protected void WriteInputStreamToRequest(Stream input, Stream output)
1008
BinaryWriter w = new BinaryWriter(output);
1009
const int size = 4096;
1010
byte[] bytes = new byte[4096];
1013
while((numBytes = input.Read(bytes, 0, size)) > 0)
1015
w.Write(bytes, 0, numBytes);
1021
//////////////////////////////////////////////////////////////////////
1022
/// <summary>creates a new feed instance to be returned by
1023
/// Batch(), Query() and other operations
1025
/// Subclasses can supply their own feed implementation by
1026
/// overriding this method.
1028
//////////////////////////////////////////////////////////////////////
1029
protected virtual AtomFeed CreateFeed(Uri uriToUse)
1031
ServiceEventArgs args = null;
1032
AtomFeed feed = null;
1034
if (this.NewFeed != null)
1036
args = new ServiceEventArgs(uriToUse, this);
1037
this.NewFeed(this, args);
1047
feed = new AtomFeed(uriToUse, this);
1055
/// takes a given feed, and does a batch post of that feed
1056
/// against the batchUri parameter. If that one is NULL
1057
/// it will try to use the batch link URI in the feed
1059
/// <param name="feed">the feed to post</param>
1060
/// <param name="batchUri">the URI to user</param>
1061
/// <returns>the returned AtomFeed</returns>
1062
public AtomFeed Batch(AtomFeed feed, Uri batchUri)
1064
return Batch(feed, batchUri, null);
1066
//////////////////////////////////////////////////////////////////////
1071
/// takes a given feed, and does a batch post of that feed
1072
/// against the batchUri parameter. If that one is NULL
1073
/// it will try to use the batch link URI in the feed
1075
/// <param name="feed">the feed to post</param>
1076
/// <param name="batchUri">the URI to user</param>
1077
/// <param name="data">The async data payload</param>
1078
/// <returns>the returned AtomFeed</returns>
1079
private AtomFeed Batch(AtomFeed feed, Uri batchUri, AsyncSendData data)
1081
Uri uriToUse = batchUri;
1084
throw new ArgumentNullException("feed");
1087
if (uriToUse == null)
1089
uriToUse = feed.Batch == null ? null : new Uri(feed.Batch);
1092
if (uriToUse == null)
1094
throw new ArgumentNullException("batchUri");
1097
Tracing.Assert(feed != null, "feed should not be null");
1100
throw new ArgumentNullException("feed");
1103
if (feed.BatchData == null)
1105
// setting this will make the feed output the namespace, instead of each entry
1106
feed.BatchData = new GDataBatchFeedData();
1108
Stream returnStream = EntrySend(uriToUse, feed, GDataRequestType.Batch, data);
1109
return CreateAndParseFeed(returnStream, uriToUse);;
1111
//////////////////////////////////////////////////////////////////////
1116
//////////////////////////////////////////////////////////////////////
1117
/// <summary>deletes an Atom entry object</summary>
1118
/// <param name="entry"> </param>
1119
//////////////////////////////////////////////////////////////////////
1120
public void Delete(AtomEntry entry)
1122
Tracing.Assert(entry != null, "entry should not be null");
1127
throw new ArgumentNullException("entry");
1130
if (entry.ReadOnly == true)
1132
throw new GDataRequestException("Can not update a read-only entry");
1135
Tracing.Assert(entry.EditUri != null, "Entry should have a valid edit URI");
1137
ISupportsEtag eSource = entry as ISupportsEtag;
1139
if (eSource != null)
1141
eTag = eSource.Etag;
1144
if (entry.EditUri != null)
1146
Delete(new Uri(entry.EditUri.ToString()), eTag);
1150
throw new GDataRequestException("Invalid Entry object (no edit uri) to call Delete on");
1153
/////////////////////////////////////////////////////////////////////////////
1155
//////////////////////////////////////////////////////////////////////
1156
///<summary>Deletes an Atom entry when given a Uri</summary>
1157
///<param name="uriTarget">The target Uri to call http delete against</param>
1158
/////////////////////////////////////////////////////////////////////
1159
public void Delete(Uri uriTarget)
1161
Delete(uriTarget, null);
1163
//////////////////////////////////////////////////////////////////////
1166
//////////////////////////////////////////////////////////////////////
1167
///<summary>Deletes an Atom entry when given a Uri</summary>
1168
///<param name="uriTarget">The target Uri to call http delete against</param>
1169
///<param name="eTag">The eTag of the item to delete. This parameter is used for strong
1170
/// concurrency support in protocol version 2 and up</param>
1171
/////////////////////////////////////////////////////////////////////
1172
public void Delete(Uri uriTarget, string eTag)
1174
Tracing.Assert(uriTarget != null, "uri should not be null");
1175
if (uriTarget == null)
1177
throw new ArgumentNullException("uriTarget");
1180
Tracing.TraceMsg("Deleting entry: " + uriTarget.ToString());
1181
IGDataRequest request = RequestFactory.CreateRequest(GDataRequestType.Delete, uriTarget);
1183
ISupportsEtag eTarget = request as ISupportsEtag;
1184
if (eTarget != null && eTag != null)
1186
eTarget.Etag = eTag;
1189
request.Credentials = Credentials;
1191
IDisposable disp = request as IDisposable;
1194
//////////////////////////////////////////////////////////////////////
1197
//////////////////////////////////////////////////////////////////////
1198
/// <summary>eventchaining. We catch this by the baseFeedParsers, which
1199
/// would not do anything with the gathered data. We pass the event up
1200
/// to the user</summary>
1201
/// <param name="sender"> the object which send the event</param>
1202
/// <param name="e">FeedParserEventArguments, holds the feedentry</param>
1203
/// <returns> </returns>
1204
//////////////////////////////////////////////////////////////////////
1205
protected void OnParsedNewEntry(object sender, FeedParserEventArgs e)
1209
throw new ArgumentNullException("e");
1211
if (this.NewAtomEntry != null)
1213
// just forward it upstream, if hooked
1214
Tracing.TraceMsg("\t calling event dispatcher");
1215
this.NewAtomEntry(this, e);
1218
/////////////////////////////////////////////////////////////////////////////
1221
//////////////////////////////////////////////////////////////////////
1222
/// <summary>eventchaining. We catch this by the baseFeedParsers, which
1223
/// would not do anything with the gathered data. We pass the event up
1224
/// to the user, and if he did not dicscard it, we add the entry to our
1225
/// collection</summary>
1226
/// <param name="sender"> the object which send the event</param>
1227
/// <param name="e">FeedParserEventArguments, holds the feedentry</param>
1228
/// <returns> </returns>
1229
//////////////////////////////////////////////////////////////////////
1230
protected void OnNewExtensionElement(object sender, ExtensionElementEventArgs e)
1232
// by default, if our event chain is not hooked, the underlying parser will add it
1233
Tracing.TraceCall("received new extension element notification");
1234
Tracing.Assert(e != null, "e should not be null");
1237
throw new ArgumentNullException("e");
1239
if (this.NewExtensionElement != null)
1241
Tracing.TraceMsg("\t calling event dispatcher");
1242
this.NewExtensionElement(this, e);
1245
/////////////////////////////////////////////////////////////////////////////
1249
/////////////////////////////////////////////////////////////////////////////
1252
/// used to cover a return stream and add some additional data to it.
1254
public class GDataReturnStream : Stream, ISupportsEtag
1256
private string etag;
1257
private Stream innerStream;
1260
/// default constructor based on a gdatarequest object
1262
/// <param name="r"></param>
1263
public GDataReturnStream(IGDataRequest r)
1265
this.innerStream = r.GetResponseStream();
1266
ISupportsEtag ise = r as ISupportsEtag;
1269
this.etag = ise.Etag;
1274
/// default override, delegates to the real stream
1276
public override bool CanRead
1278
get { return this.innerStream.CanRead; }
1282
/// default override, delegates to the real stream
1284
public override bool CanSeek
1286
get { return this.innerStream.CanSeek; }
1290
/// default override, delegates to the real stream
1292
public override bool CanTimeout
1294
get { return this.innerStream.CanTimeout;}
1298
/// default override, delegates to the real stream
1300
public override void Close()
1302
this.innerStream.Close();
1306
/// default override, delegates to the real stream
1308
public override bool CanWrite
1310
get { return this.innerStream.CanWrite; }
1314
/// default override, delegates to the real stream
1316
public override long Length
1318
get { return this.innerStream.Length; }
1322
/// default override, delegates to the real stream
1324
public override long Position
1328
return this.innerStream.Position;
1332
this.innerStream.Position = value;
1337
/// default override, delegates to the real stream
1339
public override void Flush()
1341
this.innerStream.Flush();
1345
/// default override, delegates to the real stream
1347
/// <param name="offset"></param>
1348
/// <param name="origin"></param>
1349
public override long Seek(long offset, SeekOrigin origin)
1351
return this.innerStream.Seek(offset, origin);
1355
/// default override, delegates to the real stream
1357
/// <param name="value"></param>
1358
public override void SetLength(long value)
1360
this.innerStream.SetLength(value);
1364
/// default override, delegates to the real stream
1366
/// <param name="buffer"></param>
1367
/// <param name="count"/>
1368
/// <param name="offset"/>
1369
public override int Read(byte[] buffer, int offset, int count)
1371
return this.innerStream.Read(buffer, offset, count);
1375
/// default override, delegates to the real stream
1377
/// <param name="buffer"/>
1378
/// <param name="count"/>
1379
/// <param name="offset"/>
1380
public override void Write(byte[] buffer, int offset, int count)
1382
this.innerStream.Write(buffer, offset, count);
1386
/// implements the etag interface
1390
get { return this.etag; }
1391
set { this.etag = value; }
1395
/////////////////////////////////////////////////////////////////////////////