googletastic 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. data/README.textile +47 -0
  2. data/Rakefile +103 -0
  3. data/lib/googletastic/access_rule.rb +45 -0
  4. data/lib/googletastic/album.rb +22 -0
  5. data/lib/googletastic/app_engine.rb +103 -0
  6. data/lib/googletastic/attendee.rb +7 -0
  7. data/lib/googletastic/base.rb +46 -0
  8. data/lib/googletastic/calendar.rb +60 -0
  9. data/lib/googletastic/comment.rb +3 -0
  10. data/lib/googletastic/document.rb +219 -0
  11. data/lib/googletastic/event.rb +149 -0
  12. data/lib/googletastic/ext/file.rb +219 -0
  13. data/lib/googletastic/ext/pretty_print.xsl +47 -0
  14. data/lib/googletastic/ext/xml.rb +10 -0
  15. data/lib/googletastic/ext.rb +1 -0
  16. data/lib/googletastic/form.rb +131 -0
  17. data/lib/googletastic/group.rb +44 -0
  18. data/lib/googletastic/helpers/document.rb +70 -0
  19. data/lib/googletastic/helpers/event.rb +26 -0
  20. data/lib/googletastic/helpers/form.rb +102 -0
  21. data/lib/googletastic/helpers.rb +18 -0
  22. data/lib/googletastic/image.rb +121 -0
  23. data/lib/googletastic/mixins/actions.rb +113 -0
  24. data/lib/googletastic/mixins/attributes.rb +27 -0
  25. data/lib/googletastic/mixins/finders.rb +78 -0
  26. data/lib/googletastic/mixins/namespaces.rb +42 -0
  27. data/lib/googletastic/mixins/parsing.rb +68 -0
  28. data/lib/googletastic/mixins/requesting.rb +84 -0
  29. data/lib/googletastic/mixins.rb +5 -0
  30. data/lib/googletastic/person.rb +41 -0
  31. data/lib/googletastic/spreadsheet.rb +35 -0
  32. data/lib/googletastic/sync/document.rb +120 -0
  33. data/lib/googletastic/sync/form.rb +106 -0
  34. data/lib/googletastic/sync.rb +46 -0
  35. data/lib/googletastic/thumbnail.rb +7 -0
  36. data/lib/googletastic/youtube.rb +25 -0
  37. data/lib/googletastic.rb +115 -0
  38. data/spec/benchmarks/document_benchmark.rb +40 -0
  39. data/spec/config.yml +6 -0
  40. data/spec/fixtures/data/Doing business in the eMarketPlace.doc +0 -0
  41. data/spec/fixtures/data/basic.txt +1 -0
  42. data/spec/fixtures/data/calendar.list.xml +64 -0
  43. data/spec/fixtures/data/doc_as_html.html +3097 -0
  44. data/spec/fixtures/data/doc_as_html_html.html +0 -0
  45. data/spec/fixtures/data/doclist.xml +76 -0
  46. data/spec/fixtures/data/document.single.xml +30 -0
  47. data/spec/fixtures/data/end.xml +446 -0
  48. data/spec/fixtures/data/event.list.xml +62 -0
  49. data/spec/fixtures/data/form.html +66 -0
  50. data/spec/fixtures/data/group.list.xml +58 -0
  51. data/spec/fixtures/data/person.list.xml +53 -0
  52. data/spec/fixtures/data/photo.list.xml +111 -0
  53. data/spec/fixtures/data/sample_upload.mp4 +0 -0
  54. data/spec/fixtures/data/spreadsheet.list.xml +43 -0
  55. data/spec/fixtures/models/document.rb +7 -0
  56. data/spec/fixtures/models/event.rb +3 -0
  57. data/spec/fixtures/models/form.rb +6 -0
  58. data/spec/fixtures/models/test_model.rb +3 -0
  59. data/spec/fixtures/results/test.txt +185 -0
  60. data/spec/googletastic/access_rule_spec.rb +13 -0
  61. data/spec/googletastic/album_spec.rb +9 -0
  62. data/spec/googletastic/app_engine_spec.rb +12 -0
  63. data/spec/googletastic/base_spec.rb +49 -0
  64. data/spec/googletastic/calendar_spec.rb +11 -0
  65. data/spec/googletastic/document_spec.rb +163 -0
  66. data/spec/googletastic/event_spec.rb +34 -0
  67. data/spec/googletastic/form_spec.rb +43 -0
  68. data/spec/googletastic/group_spec.rb +9 -0
  69. data/spec/googletastic/image_spec.rb +9 -0
  70. data/spec/googletastic/person_spec.rb +9 -0
  71. data/spec/googletastic/post_spec.rb +46 -0
  72. data/spec/googletastic/spreadsheet_spec.rb +9 -0
  73. data/spec/googletastic/youtube_spec.rb +58 -0
  74. data/spec/spec.opts +1 -0
  75. data/spec/spec_helper.rb +25 -0
  76. metadata +187 -0
@@ -0,0 +1,41 @@
1
+ class Googletastic::Person < Googletastic::Base
2
+
3
+ attr_accessor :name, :email
4
+
5
+ class << self
6
+
7
+ def index_url
8
+ "http://www.google.com/m8/feeds/contacts/default/full"
9
+ end
10
+
11
+ def client_class
12
+ "Contacts"
13
+ end
14
+
15
+ # http://code.google.com/apis/contacts/docs/2.0/reference.html#Parameters
16
+ def valid_queries
17
+ {
18
+ :order => "orderby",
19
+ :sort => "sortorder",
20
+ :group => "group"
21
+ }.merge(super)
22
+ end
23
+
24
+ def unmarshall(xml)
25
+ records = xml.xpath("//atom:entry", ns_tag("atom")).collect do |record|
26
+ id = record.xpath("atom:id", ns_tag("atom")).first.text
27
+ name = record.xpath("atom:title", ns_tag("atom")).first.text
28
+ email = record.xpath("gd:email", ns_tag("gd")).first["address"].to_s
29
+
30
+ Googletastic::Person.new(
31
+ :id => id,
32
+ :name => name,
33
+ :email => email
34
+ )
35
+ end
36
+ records
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,35 @@
1
+ class Googletastic::Spreadsheet < Googletastic::Base
2
+
3
+ attr_accessor :title, :content
4
+
5
+ # Time.now.xmlschema
6
+ class << self
7
+
8
+ def client_class
9
+ "Spreadsheets"
10
+ end
11
+
12
+ def index_url
13
+ "http://spreadsheets.google.com/feeds/spreadsheets/private/full"
14
+ end
15
+
16
+ def unmarshall(xml)
17
+ records = xml.xpath("//atom:entry", ns_tag("atom")).collect do |record|
18
+ id = record.xpath("atom:id", ns_tag("atom")).first.text.gsub("http://spreadsheets.google.com/feeds/spreadsheets/", "")
19
+ title = record.xpath("atom:title", ns_tag("atom")).first.text
20
+ content = record.xpath("atom:content", ns_tag("atom")).first.text
21
+ created_at = record.xpath("atom:published", ns_tag("atom")).text
22
+ updated_at = record.xpath("atom:updated", ns_tag("atom")).text
23
+
24
+ Googletastic::Spreadsheet.new(
25
+ :id => id,
26
+ :title => title,
27
+ :content => content,
28
+ :updated_at => DateTime.parse(updated_at)
29
+ )
30
+ end
31
+ records
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,120 @@
1
+ module Googletastic::Sync::Document
2
+
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ def sync(documents, options, &block)
10
+ updated = process(documents, options)
11
+ key = options[:key].to_s
12
+ data = {key => []}
13
+ documents.each do |document|
14
+ data[key] << {
15
+ :id => document.remote.id
16
+ }
17
+ yield(data, key, document) if block_given?
18
+ end
19
+ response = Googletastic::Sync.post(
20
+ :url => options[:url],
21
+ :path => options[:path],
22
+ :format => :json,
23
+ :data => data
24
+ )
25
+ end
26
+
27
+ def process(documents, options = {})
28
+ options[:key] ||= "documents"
29
+ # defaults to a reasonable limit (3MB) for heroku
30
+ options[:max_size] ||= 3000000
31
+ # per max_size chunk
32
+ documents_processed = []
33
+ # total
34
+ updated_documents = []
35
+ counted_size = 0
36
+ documents.each_with_index do |document, index|
37
+ next if document.remote.kind != "document"
38
+ if document.synced_at and document.synced_at >= document.remote.updated_at
39
+ puts "Skipping Document... #{document.title}"
40
+ if documents_processed.length > 0 and index == documents.length - 1
41
+ Googletastic::Sync.push(options[:username], options[:password], options)
42
+ cleanup(documents_processed, options)
43
+ end
44
+ next
45
+ end
46
+ remote = document.remote
47
+ content = nil
48
+ title = remote.title
49
+ ext = remote.ext
50
+ puts "Processing Document... #{document.title}"
51
+ begin
52
+ if ext == ".textile"
53
+ # google is putting strange characters at beginning of downloaded files
54
+ content = remote.download("txt").body.gsub(/\357\273\277/, "")
55
+ content = RedCloth.new(content).to_html
56
+ elsif ext == ".markdown"
57
+ content = remote.download("txt").body.gsub(/\357\273\277/, "")
58
+ content = BlueCloth.new(content).to_html
59
+ elsif ext.nil? || ext.empty?
60
+ # just use the html we have already
61
+ content = remote.content
62
+ else
63
+ content = remote.download("txt").body.gsub(/\357\273\277/, "")
64
+ end
65
+
66
+ document.content = content
67
+ title = remote.id
68
+
69
+ tempfile = Tempfile.new("googltastic-tempfiles-#{title}-#{Time.now}-#{rand(10000)}")
70
+ tempfile.write(content)
71
+
72
+ path = File.join(options[:folder], options[:key])
73
+ Dir.mkdir(path) unless File.exists?(path)
74
+ path = File.join(path, title)
75
+
76
+ # if we have passed the 5MB (or our 3MB) limit,
77
+ # then push the files to GAE
78
+ if tempfile.size + counted_size >= options[:max_size] || index == documents.length - 1
79
+ Googletastic::Sync.push(options[:username], options[:password], options)
80
+ cleanup(documents_processed, options)
81
+ counted_size = 0
82
+ documents_processed = []
83
+ end
84
+
85
+ content = Googletastic::PrettyPrint.xml(content)
86
+
87
+ File.open(path, 'w') {|f| f.write(content) }
88
+ documents_processed << document
89
+ counted_size += tempfile.size
90
+ tempfile.close
91
+
92
+ updated_documents << document
93
+
94
+ rescue Exception => e
95
+ puts "Error... #{e.inspect}"
96
+ end
97
+ end
98
+
99
+ updated_documents
100
+ end
101
+
102
+ def cleanup(syncables, options)
103
+ syncables.each do |syncable|
104
+ syncable.synced_at = Time.now
105
+ syncable.save!
106
+ path = File.join(options[:folder], options[:key], syncable.remote.id)
107
+ path += options[:ext] if options.has_key?(:ext)
108
+ begin
109
+ File.delete(path)
110
+ rescue Exception => e
111
+
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ Googletastic::Document.class_eval do
119
+ include Googletastic::Sync::Document
120
+ end
@@ -0,0 +1,106 @@
1
+ module Googletastic::Sync::Form
2
+
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ def sync(forms, options = {}, &block)
10
+ updated = process(forms, options)
11
+ key = options[:key].to_s
12
+ data = {key => []}
13
+ updated.each do |form|
14
+ data[key] << {
15
+ :id => form.remote.id,
16
+ :formkey => form.formkey
17
+ }
18
+ yield(data, key, form) if block_given?
19
+ end
20
+ response = Googletastic::Sync.post(
21
+ :url => options[:url],
22
+ :path => options[:path],
23
+ :format => :json,
24
+ :data => data
25
+ )
26
+ end
27
+
28
+ def process(forms, options = {})
29
+ options[:key] ||= "forms"
30
+ # defaults to a reasonable limit (3MB) for heroku
31
+ options[:max_size] ||= 3000000
32
+ # per max_size chunk
33
+ forms_processed = []
34
+ # total
35
+ updated_forms = []
36
+ counted_size = 0
37
+ forms.each_with_index do |form, index|
38
+ if form.formkey.nil?
39
+ puts "Missing formkey... #{form.title}"
40
+ if forms_processed.length > 0 and index == forms.length - 1
41
+ Googletastic::Sync.push(options[:username], options[:password], options)
42
+ cleanup(forms_processed, options.merge(:ext => ".html"))
43
+ end
44
+ next
45
+ end
46
+ remote = form.remote
47
+ content = nil
48
+ puts "Processing Form... #{form.title}"
49
+ begin
50
+ form.remote.form_key = form.formkey
51
+ title = form.formkey + ".html"
52
+ content = form.remote.body
53
+
54
+ tempfile = Tempfile.new("googltastic-form-tempfiles-#{title}-#{Time.now}-#{rand(10000)}")
55
+ tempfile.write(content)
56
+
57
+ path = File.join(options[:folder], options[:key])
58
+ Dir.mkdir(path) unless File.exists?(path)
59
+ path = File.join(path, title)
60
+
61
+ # if we have passed the 5MB (or our 3MB) limit,
62
+ # then push the files to GAE
63
+ if tempfile.size + counted_size >= options[:max_size] || index == forms.length - 1
64
+ Googletastic::Sync.push(options[:username], options[:password], options)
65
+ cleanup(forms_processed, options.merge(:ext => ".html"))
66
+ counted_size = 0
67
+ forms_processed = []
68
+ end
69
+
70
+ content = Googletastic::PrettyPrint.xml(content)
71
+
72
+ File.open(path, 'w') {|f| f.write(content) }
73
+ forms_processed << form
74
+ counted_size += tempfile.size
75
+ tempfile.close
76
+
77
+ updated_forms << form
78
+
79
+ rescue Exception => e
80
+ puts "Error... #{e.inspect}"
81
+ end
82
+ end
83
+
84
+ updated_forms
85
+ end
86
+
87
+ def cleanup(syncables, options)
88
+ syncables.each do |syncable|
89
+ syncable.synced_at = Time.now
90
+ syncable.save!
91
+ path = File.join(options[:folder], options[:key], syncable.formkey)
92
+ path += options[:ext] if options.has_key?(:ext)
93
+ begin
94
+ File.delete(path)
95
+ rescue Exception => e
96
+
97
+ end
98
+ end
99
+ end
100
+
101
+ end
102
+ end
103
+
104
+ Googletastic::Form.class_eval do
105
+ include Googletastic::Sync::Form
106
+ end
@@ -0,0 +1,46 @@
1
+ module Googletastic::Sync
2
+
3
+ class << self
4
+
5
+ def sync_documents(documents, options = {}, &block)
6
+ Googletastic::Document.sync(documents, options, &block)
7
+ end
8
+
9
+ def sync_forms(forms, options = {}, &block)
10
+ Googletastic::Form.sync(forms, options, &block)
11
+ end
12
+
13
+ def push(username, password, options = {})
14
+ Googletastic.client_for(:app_engine).push(username, password, options)
15
+ end
16
+
17
+ # POSTs to your registered application
18
+ def post(options = {})
19
+ url = URI.parse(options[:url])
20
+ # POST update to registered application
21
+ http = Net::HTTP.new(url.host, url.port)
22
+ header = options[:header] || {}
23
+ header.merge!('Content-Type' =>'application/json')
24
+ data = options[:data].to_json
25
+ response = http.post(options[:path], data, header)
26
+ end
27
+
28
+ def cleanup(syncables, options)
29
+ syncables.each do |syncable|
30
+ syncable.synced_at = Time.now
31
+ syncable.save!
32
+ path = File.join(options[:folder], syncable.title)
33
+ path += options[:ext] if options.has_key?(:ext)
34
+ begin
35
+ File.delete(path)
36
+ rescue Exception => e
37
+
38
+ end
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+
46
+ require_local "sync/*", __FILE__
@@ -0,0 +1,7 @@
1
+ class Googletastic::Thumbnail < Hash # simple value object
2
+
3
+ include Googletastic::Mixins::Attributes
4
+
5
+ attr_accessor :url, :width, :height
6
+
7
+ end
@@ -0,0 +1,25 @@
1
+ =begin
2
+ module Thing
3
+ module Google
4
+ class YouTube < CouchRest::ExtendedDocument
5
+
6
+ MEDIA_NS = "http://search.yahoo.com/mrss" unless defined?(MEDIA_NS)
7
+ YT_NS = "http://gdata.youtube.com/schemas/2007" unless defined?(YT_NS)
8
+
9
+ API = "http://gdata.youtube.com" unless defined?(API)
10
+ GET_UPLOAD_TOKEN = "action/GetUploadToken"
11
+ UPLOAD = "http://uploads.gdata.youtube.com/feeds/api/users"
12
+ MULTIPART = "multipart/related"
13
+
14
+ def self.unmarshall(object)
15
+
16
+ end
17
+
18
+ def self.marshall(xml)
19
+
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ =end
@@ -0,0 +1,115 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+ require 'rake/clean'
5
+ #require 'rbconfig'
6
+ require 'open-uri'
7
+ require 'nokogiri'
8
+ require 'active_support'
9
+ require 'active_record'
10
+ require 'gdata'
11
+ require 'liquid'
12
+
13
+ GOOGLETASTIC_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(GOOGLETASTIC_ROOT)
14
+
15
+ class Module
16
+ def include_class_and_instance_modules
17
+ self.module_eval <<-eos
18
+ def self.included(base)
19
+ base.extend(ClassMethods)
20
+ base.class_eval do
21
+ include InstanceMethods
22
+ end
23
+ end
24
+ eos
25
+ end
26
+ end
27
+
28
+ def require_local(path, from = __FILE__)
29
+ files(path, from) {|file| require file if File.extname(file) == ".rb"}
30
+ end
31
+
32
+ def files(path, from = __FILE__, &block)
33
+ Dir.glob(File.expand_path(File.join(File.dirname(from), path))) {|file| yield file}
34
+ end
35
+
36
+ def require_spec
37
+ require File.join(GOOGLETASTIC_ROOT, "/spec/spec_helper")
38
+ end
39
+
40
+ class String
41
+ # active_support isn't letting me do this!
42
+ def parameterize(sep = '-')
43
+ # replace accented chars with ther ascii equivalents
44
+ # parameterized_string = transliterate(string)
45
+ parameterized_string = self.dup
46
+ # Turn unwanted chars into the seperator
47
+ parameterized_string.gsub!(/[^a-z0-9\-_\+]+/i, sep)
48
+ unless sep.blank?
49
+ re_sep = Regexp.escape(sep)
50
+ # No more than one of the separator in a row.
51
+ parameterized_string.gsub!(/#{re_sep}{2,}/, sep)
52
+ # Remove leading/trailing separator.
53
+ parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '')
54
+ end
55
+ parameterized_string.downcase
56
+ end
57
+ end
58
+
59
+ module Googletastic
60
+ # :stopdoc:
61
+ VERSION = '0.0.1'
62
+ # :startdoc
63
+ class << self; attr_accessor :keys, :clients, :options; end
64
+
65
+ def self.credentials
66
+ return self.keys if self.keys
67
+
68
+ config_file = "#{RAILS_ROOT}/config/gdata.yml"
69
+ raise "Sorry, you must have #{config_file}" unless File.exists?(config_file)
70
+ self.keys = YAML.load_file(config_file).symbolize_keys
71
+ end
72
+
73
+ def self.client_for(model)
74
+ self.clients ||= {}
75
+ model = model.to_sym
76
+ return self.clients[model] if self.clients.has_key?(model)
77
+ client = ("GData::Client::#{model.to_s.camelize}").constantize.new(:source => credentials[:application])
78
+ client.clientlogin(credentials[:username], credentials[:password], nil, nil, nil, credentials[:account_type])
79
+ self.clients[model] = client
80
+ end
81
+
82
+ def self.options_for(klass, value = {})
83
+ self.options ||= {}
84
+ klass = klass.is_a?(Class) ? klass : klass.class
85
+ name = klass.to_s.underscore.downcase.to_sym
86
+ if value and !value.blank?
87
+ value.symbolize_keys!
88
+ self.options[name] = value
89
+ else
90
+ self.options[name] ||= {}
91
+ end
92
+ self.options[name]
93
+ end
94
+
95
+ def self.[](value)
96
+ self.options_for(value)
97
+ end
98
+ end
99
+
100
+ # main includes
101
+ require_local "googletastic/ext/*"
102
+
103
+ require File.dirname(__FILE__) + '/googletastic/mixins'
104
+ require File.dirname(__FILE__) + '/googletastic/base'
105
+
106
+ require_local "googletastic/*"
107
+
108
+ #files("googletastic/mixins/*") do |file|
109
+ # Googletastic::Base.send(:include, "Googletastic::Mixins::#{File.basename(file, '.rb').camelize}".constantize)
110
+ #end
111
+
112
+ if defined?(ActiveRecord::Base)
113
+ ActiveRecord::Base.send(:include, Googletastic::Helpers)
114
+ end
115
+ # EOF
@@ -0,0 +1,40 @@
1
+ require 'benchmark'
2
+ require 'rubygems'
3
+
4
+ $:.unshift(File.dirname(__FILE__) + '/..')
5
+ require 'spec_helper'
6
+
7
+ id = "0AT1-fvkm5vJPZGN2NzR3bmJfMGZiMnh3N2R0"
8
+ many = 1
9
+
10
+ Benchmark.bmbm(7) do |x|
11
+ x.report ("Find the Document") do
12
+ many.times do |i|
13
+ doc = Googletastic::Document.find(id)
14
+ end
15
+ end
16
+ x.report("View the Raw HTML: ") do
17
+ many.times do |i|
18
+ doc = Googletastic::Document.find(id)
19
+ result = doc.view
20
+ end
21
+ end
22
+ x.report("Download as HTML: ") do
23
+ many.times do |i|
24
+ doc = Googletastic::Document.find(id)
25
+ result = doc.download("html")
26
+ end
27
+ end
28
+ x.report("Download as TXT: ") do
29
+ many.times do |i|
30
+ doc = Googletastic::Document.find(id)
31
+ result = doc.download("txt")
32
+ end
33
+ end
34
+ x.report("Download as PDF: ") do
35
+ many.times do |i|
36
+ doc = Googletastic::Document.find(id)
37
+ result = doc.download("pdf")
38
+ end
39
+ end
40
+ end
data/spec/config.yml ADDED
@@ -0,0 +1,6 @@
1
+ username: "flexinstyle@gmail.com"
2
+ password: "flexyflexy"
3
+ application: "Googletastic Test"
4
+ account_type: "HOSTED_OR_GOOGLE"
5
+ youtube_login: "MrFlexinstyle"
6
+ youtube_dev_key: "AI39si7jkhs_ECjF4unOQz8gpWGSKXgq0KJpm8wywkvBSw4s8oJd5p5vkpvURHBNh-hiYJtoKwQqSfot7KoCkeCE32rNcZqMxA"
@@ -0,0 +1 @@
1
+ something
@@ -0,0 +1,64 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <feed xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app" xmlns:gCal="http://schemas.google.com/gCal/2005" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:gd="http://schemas.google.com/g/2005" gd:etag="W/&quot;AkACRXkyeSp7IWA9WxBaEk8.&quot;" gd:kind="calendar#calendarFeed">
3
+ <id>http://www.google.com/calendar/feeds/default/allcalendars/full</id>
4
+ <updated>2010-03-22T03:32:44.791Z</updated>
5
+ <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/gCal/2005#calendarmeta"/>
6
+ <title>flexinstyle@gmail.com's Calendar List</title>
7
+ <link rel="alternate" type="text/html" href="http://www.google.com/calendar/render"/>
8
+ <link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full"/>
9
+ <link rel="http://schemas.google.com/g/2005#post" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full"/>
10
+ <link rel="self" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full"/>
11
+ <author>
12
+ <name>flexinstyle@gmail.com</name>
13
+ <email>flexinstyle@gmail.com</email>
14
+ </author>
15
+ <generator version="1.0" uri="http://www.google.com/calendar">Google Calendar</generator>
16
+ <openSearch:startIndex>1</openSearch:startIndex>
17
+ <entry gd:etag="W/&quot;A0cFSX47eCp7IWA9WxBaEU0.&quot;" gd:kind="calendar#calendar">
18
+ <id>http://www.google.com/calendar/feeds/default/calendars/flexinstyle%40gmail.com</id>
19
+ <published>2010-03-22T03:32:44.788Z</published>
20
+ <updated>2010-03-20T18:16:58.000Z</updated>
21
+ <app:edited>2010-03-20T18:16:58.000Z</app:edited>
22
+ <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/gCal/2005#calendarmeta"/>
23
+ <title type="text">flexinstyle@gmail.com</title>
24
+ <content type="application/atom+xml" src="http://www.google.com/calendar/feeds/flexinstyle%40gmail.com/private/full"/>
25
+ <link rel="alternate" type="application/atom+xml" href="http://www.google.com/calendar/feeds/flexinstyle%40gmail.com/private/full"/>
26
+ <link rel="http://schemas.google.com/gCal/2005#eventFeed" type="application/atom+xml" href="http://www.google.com/calendar/feeds/flexinstyle%40gmail.com/private/full"/>
27
+ <link rel="http://schemas.google.com/acl/2007#accessControlList" type="application/atom+xml" href="http://www.google.com/calendar/feeds/flexinstyle%40gmail.com/acl/full"/>
28
+ <link rel="self" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full/flexinstyle%40gmail.com"/>
29
+ <link rel="edit" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full/flexinstyle%40gmail.com"/>
30
+ <author>
31
+ <name>flexinstyle@gmail.com</name>
32
+ <email>flexinstyle@gmail.com</email>
33
+ </author>
34
+ <gCal:accesslevel value="owner"/>
35
+ <gCal:color value="#A32929"/>
36
+ <gCal:hidden value="false"/>
37
+ <gCal:selected value="true"/>
38
+ <gCal:timezone value="America/Phoenix"/>
39
+ <gCal:timesCleaned value="0"/>
40
+ </entry>
41
+ <entry gd:etag="W/&quot;A0QBRX47eCp7IWA9WxBaEk0.&quot;" gd:kind="calendar#calendar">
42
+ <id>http://www.google.com/calendar/feeds/default/calendars/en.usa%23holiday%40group.v.calendar.google.com</id>
43
+ <published>2010-03-22T03:32:44.788Z</published>
44
+ <updated>2010-03-21T22:09:14.000Z</updated>
45
+ <app:edited>2010-03-21T22:09:14.000Z</app:edited>
46
+ <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/gCal/2005#calendarmeta"/>
47
+ <title type="text">US Holidays</title>
48
+ <summary type="text">US Holidays</summary>
49
+ <content type="application/atom+xml" src="http://www.google.com/calendar/feeds/en.usa%23holiday%40group.v.calendar.google.com/private/full"/>
50
+ <link rel="alternate" type="application/atom+xml" href="http://www.google.com/calendar/feeds/en.usa%23holiday%40group.v.calendar.google.com/private/full"/>
51
+ <link rel="http://schemas.google.com/gCal/2005#eventFeed" type="application/atom+xml" href="http://www.google.com/calendar/feeds/en.usa%23holiday%40group.v.calendar.google.com/private/full"/>
52
+ <link rel="self" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full/en.usa%23holiday%40group.v.calendar.google.com"/>
53
+ <link rel="edit" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full/en.usa%23holiday%40group.v.calendar.google.com"/>
54
+ <author>
55
+ <name>US Holidays</name>
56
+ </author>
57
+ <gCal:accesslevel value="read"/>
58
+ <gCal:color value="#2952A3"/>
59
+ <gCal:hidden value="false"/>
60
+ <gCal:selected value="true"/>
61
+ <gCal:timezone value="America/Phoenix"/>
62
+ <gCal:timesCleaned value="0"/>
63
+ </entry>
64
+ </feed>