google_calendar 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Gems required to use google_calendar.
4
+ gem "nokogiri", ">= 1.4.4"
5
+ gem "addressable", ">= 2.2.2"
6
+
7
+ # Gem development dependencies.
8
+ group :development do
9
+ gem "shoulda", ">= 0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.1"
12
+ gem "rcov", ">= 0"
13
+ gem "mocha", ">= 0"
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.2.2)
5
+ git (1.2.5)
6
+ jeweler (1.5.1)
7
+ bundler (~> 1.0.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ mocha (0.9.10)
11
+ rake
12
+ nokogiri (1.4.4)
13
+ rake (0.8.7)
14
+ rcov (0.9.9)
15
+ shoulda (2.11.3)
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ addressable (>= 2.2.2)
22
+ bundler (~> 1.0.0)
23
+ jeweler (~> 1.5.1)
24
+ mocha
25
+ nokogiri (>= 1.4.4)
26
+ rcov
27
+ shoulda
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Steve Zich
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,52 @@
1
+ = Google Calendar
2
+
3
+ A fast lightweight and minimalist wrapper around the google calendar api.
4
+
5
+ == Install
6
+ [sudo] gem install 'google_calendar'
7
+
8
+ Note: Make sure to customize the APP_NAME constant in the Connection class.
9
+
10
+ == Usage
11
+ require 'rubygems'
12
+ require 'google_calendar'
13
+
14
+ cal = Google::Calendar.new(:username => 'some.person@gmail.com', :password => 'super-secret')
15
+
16
+ event = cal.create_event do |e|
17
+ e.title = 'A Cool Event'
18
+ e.start_time = Time.now
19
+ e.end_time = Time.now + (60 * 60) # seconds * min
20
+ end
21
+
22
+ puts event
23
+
24
+ event = cal.find_or_create_event_by_id(event.id) do |e|
25
+ e.title = 'An Updated Cool Event'
26
+ e.end_time = Time.now + (60 * 60 * 2) # seconds * min * hours
27
+ end
28
+
29
+ puts event
30
+
31
+ # All events
32
+ puts cal.events
33
+
34
+ # Query events
35
+ puts cal.find_events('my search string')
36
+
37
+
38
+ Note: This is not a complete implementation of the calendar api, it just includes the features I needed to support our internal calendar integration.
39
+
40
+ == Contributing to google_calendar
41
+
42
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
43
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
44
+ * Fork the project
45
+ * Start a feature/bugfix branch
46
+ * Commit and push until you are happy with your contribution
47
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
48
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
49
+
50
+ == Copyright
51
+
52
+ Copyright (c) 2010 Steve Zich. See LICENSE.txt for further details.
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "google_calendar"
16
+ gem.homepage = "http://github.com/northworld/google_calendar"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{A lightweight google calendar API wrapper}
19
+ gem.description = %Q{A minimal wrapper around the google calendar API, which uses nokogiri for fast parsing.}
20
+ gem.email = "steve.zich@gmail.com"
21
+ gem.authors = ["Steve Zich"]
22
+ gem.add_runtime_dependency "nokogiri", ">= 1.4.4"
23
+ gem.add_runtime_dependency "addressable", ">= 2.2.2"
24
+ gem.add_development_dependency "shoulda", ">= 0"
25
+ gem.add_development_dependency "bundler", "~> 1.0.0"
26
+ gem.add_development_dependency "jeweler", "~> 1.5.1"
27
+ gem.add_development_dependency "rcov", ">= 0"
28
+ end
29
+ Jeweler::RubygemsDotOrgTasks.new
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.libs << 'lib' << 'test'
34
+ test.pattern = 'test/**/test_*.rb'
35
+ test.verbose = true
36
+ end
37
+
38
+ require 'rcov/rcovtask'
39
+ Rcov::RcovTask.new do |test|
40
+ test.libs << 'test'
41
+ test.pattern = 'test/**/test_*.rb'
42
+ test.verbose = true
43
+ end
44
+
45
+ task :default => :test
46
+
47
+ require 'rake/rdoctask'
48
+ Rake::RDocTask.new do |rdoc|
49
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "google_calendar #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,101 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{google_calendar}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Steve Zich"]
12
+ s.date = %q{2010-12-16}
13
+ s.description = %q{A minimal wrapper around the google calendar API, which uses nokogiri for fast parsing.}
14
+ s.email = %q{steve.zich@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "google_calendar.gemspec",
28
+ "lib/google/calendar.rb",
29
+ "lib/google/connection.rb",
30
+ "lib/google/errors.rb",
31
+ "lib/google/event.rb",
32
+ "lib/google/net/https.rb",
33
+ "lib/google_calendar.rb",
34
+ "test/helper.rb",
35
+ "test/mocks/create_event.xml",
36
+ "test/mocks/events.xml",
37
+ "test/mocks/find_event_by_id.xml",
38
+ "test/mocks/query_events.xml",
39
+ "test/mocks/successful_login.txt",
40
+ "test/test_google_calendar.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/northworld/google_calendar}
43
+ s.licenses = ["MIT"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.7}
46
+ s.summary = %q{A lightweight google calendar API wrapper}
47
+ s.test_files = [
48
+ "test/helper.rb",
49
+ "test/test_google_calendar.rb"
50
+ ]
51
+
52
+ if s.respond_to? :specification_version then
53
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
54
+ s.specification_version = 3
55
+
56
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
57
+ s.add_runtime_dependency(%q<nokogiri>, [">= 1.4.4"])
58
+ s.add_runtime_dependency(%q<addressable>, [">= 2.2.2"])
59
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
60
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
61
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
62
+ s.add_development_dependency(%q<rcov>, [">= 0"])
63
+ s.add_development_dependency(%q<mocha>, [">= 0"])
64
+ s.add_runtime_dependency(%q<nokogiri>, [">= 1.4.4"])
65
+ s.add_runtime_dependency(%q<addressable>, [">= 2.2.2"])
66
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
67
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
68
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
69
+ s.add_development_dependency(%q<rcov>, [">= 0"])
70
+ else
71
+ s.add_dependency(%q<nokogiri>, [">= 1.4.4"])
72
+ s.add_dependency(%q<addressable>, [">= 2.2.2"])
73
+ s.add_dependency(%q<shoulda>, [">= 0"])
74
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
75
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
76
+ s.add_dependency(%q<rcov>, [">= 0"])
77
+ s.add_dependency(%q<mocha>, [">= 0"])
78
+ s.add_dependency(%q<nokogiri>, [">= 1.4.4"])
79
+ s.add_dependency(%q<addressable>, [">= 2.2.2"])
80
+ s.add_dependency(%q<shoulda>, [">= 0"])
81
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
82
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
83
+ s.add_dependency(%q<rcov>, [">= 0"])
84
+ end
85
+ else
86
+ s.add_dependency(%q<nokogiri>, [">= 1.4.4"])
87
+ s.add_dependency(%q<addressable>, [">= 2.2.2"])
88
+ s.add_dependency(%q<shoulda>, [">= 0"])
89
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
90
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
91
+ s.add_dependency(%q<rcov>, [">= 0"])
92
+ s.add_dependency(%q<mocha>, [">= 0"])
93
+ s.add_dependency(%q<nokogiri>, [">= 1.4.4"])
94
+ s.add_dependency(%q<addressable>, [">= 2.2.2"])
95
+ s.add_dependency(%q<shoulda>, [">= 0"])
96
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
97
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
98
+ s.add_dependency(%q<rcov>, [">= 0"])
99
+ end
100
+ end
101
+
@@ -0,0 +1,138 @@
1
+ module Google
2
+
3
+ # Calendar is the main object you use to interact with events.
4
+ # use it to find, create, update and delete them.
5
+ #
6
+ class Calendar
7
+
8
+ # Setup and connect to the specified google calendar.
9
+ # the +params+ paramater accepts
10
+ # * :username => the username of the specified calendar (i.e. some.guy@gmail.com)
11
+ # * :password => the password for the specified user (i.e. super-secret)
12
+ # * :calendar => the name of the calendar you would like to work with (optional, defaults to the calendar the user setup as their default one.)
13
+ #
14
+ # After creating an instace you are immediatly logged on and ready to go.
15
+ #
16
+ # ==== Examples
17
+ # # Use the default calendar
18
+ # Calendar.new(username: => 'some.guy@gmail.com', :password => 'ilovepie!')
19
+ #
20
+ # # Specify the calendar
21
+ # Calendar.new(username: => 'some.guy@gmail.com', :password => 'ilovepie!', :calendar => 'my.company@gmail.com')
22
+ #
23
+ def initialize(params)
24
+ username = params[:username]
25
+ password = params[:password]
26
+ @calendar = params[:calendar]
27
+
28
+ @connection = Connection.new(:username => username, :password => password)
29
+ end
30
+
31
+ # Find all of the events associated with this calendar.
32
+ # Returns:
33
+ # nil if nothing found.
34
+ # a single event if only one found.
35
+ # an array of events if many found.
36
+ #
37
+ def events
38
+ event_lookup()
39
+ end
40
+
41
+ # This is equivalnt to running a search in
42
+ # the google calendar web application. Google does not provide a way to easily specify
43
+ # what attributes you would like to search (i.e. title), by default it searches everything.
44
+ # If you would like to find specific attribute value (i.e. title=Picnic), run a query
45
+ # and parse the results.
46
+ # Returns:
47
+ # nil if nothing found.
48
+ # a single event if only one found.
49
+ # an array of events if many found.
50
+ #
51
+ def find_events(query)
52
+ event_lookup("?q=#{query}")
53
+ end
54
+
55
+ # Attempts to find the event specified by the id
56
+ # Returns:
57
+ # nil if nothing found.
58
+ # a single event if only one found.
59
+ # an array of events if many found.
60
+ #
61
+ def find_event_by_id(id)
62
+ event_lookup("/#{id}")
63
+ end
64
+
65
+ # Creates a new event and immediatly saves it.
66
+ # returns the event
67
+ #
68
+ # ==== Examples
69
+ # # Use a block
70
+ # cal.create_event do |e|
71
+ # e.title = "A New Event"
72
+ # e.where = "Room 101"
73
+ # end
74
+ #
75
+ # # Don't use a block (need to call save maunally)
76
+ # event = cal.create_event
77
+ # event.title = "A New Event"
78
+ # event.where = "Room 101"
79
+ # event.save
80
+ #
81
+ def create_event(&blk)
82
+ setup_event(Event.new, &blk)
83
+ end
84
+
85
+ # looks for the spedified event id.
86
+ # If it is found it, updates it's vales and returns it.
87
+ # If the event is no longer on the server it creates a new one with the specified values.
88
+ # Works like the create_event method.
89
+ #
90
+ def find_or_create_event_by_id(id, &blk)
91
+ setup_event(find_event_by_id(id) || Event.new, &blk)
92
+ end
93
+
94
+ # Saves the specified event.
95
+ # This is a callback used by the Event class.
96
+ #
97
+ def save_event(event)
98
+ method = (event.id == nil || event.id == '') ? :post : :put
99
+ query_string = (method == :put) ? "/#{event.id}" : ''
100
+ @connection.send(Addressable::URI.parse(events_url + query_string), method, event.to_xml)
101
+ end
102
+
103
+ # Deletes the specified event.
104
+ # This is a callback used by the Event class.
105
+ #
106
+ def delete_event(event)
107
+ @connection.send(Addressable::URI.parse(events_url + "/#{event.id}"), :delete)
108
+ end
109
+
110
+ protected
111
+
112
+ def event_lookup(query_string = '') #:nodoc:
113
+ response = @connection.send(Addressable::URI.parse(events_url + query_string), :get)
114
+
115
+ return nil if response.kind_of? Net::HTTPNotFound
116
+
117
+ events = Event.build_from_google_feed(response.body, self)
118
+ events.length > 1 ? events : events[0]
119
+ end
120
+
121
+ def calendar_url #:nodoc:
122
+ @calendar || 'default'
123
+ end
124
+
125
+ def events_url #:nodoc:
126
+ "https://www.google.com/calendar/feeds/#{calendar_url}/private/full"
127
+ end
128
+
129
+ def setup_event(event) #:nodoc:
130
+ event.calendar = self
131
+ yield(event) if block_given?
132
+ event.save
133
+ event
134
+ end
135
+
136
+ end
137
+
138
+ end
@@ -0,0 +1,110 @@
1
+ require "addressable/uri"
2
+ require 'google/net/https'
3
+
4
+ module Google
5
+
6
+ # This is a utility class that performs all of the
7
+ # communication with the google calendar api.
8
+ #
9
+ class Connection
10
+ AUTH_URL = "https://www.google.com/accounts/ClientLogin"
11
+ APP_NAME = "northworld.com-googlecalendar-integration"
12
+
13
+ # set the username and password and login.
14
+ #
15
+ def initialize(params)
16
+ @username = params[:username]
17
+ @password = params[:password]
18
+
19
+ login()
20
+ end
21
+
22
+ # login to the google calendar and grab an auth token.
23
+ #
24
+ def login()
25
+ content = {
26
+ 'Email' => @username,
27
+ 'Passwd' => @password,
28
+ 'source' => APP_NAME,
29
+ 'accountType' => 'HOSTED_OR_GOOGLE',
30
+ 'service' => 'cl'}
31
+
32
+ response = send(Addressable::URI.parse(AUTH_URL), :post_form, content)
33
+
34
+ raise HTTPRequestFailed unless response.kind_of? Net::HTTPSuccess
35
+
36
+ @token = response.body.split('=').last
37
+ @headers = {
38
+ 'Authorization' => "GoogleLogin auth=#{@token}",
39
+ 'Content-Type' => 'application/atom+xml'
40
+ }
41
+ @update_header = @headers.clone
42
+ @update_header["If-Match"] = "*"
43
+ end
44
+
45
+ # send a request to google.
46
+ #
47
+ def send(uri, method, content = '', redirect_count = 10)
48
+ raise HTTPTooManyRedirections if redirect_count == 0
49
+
50
+ set_session_if_necessary(uri)
51
+
52
+ http = (uri.scheme == 'https' ? Net::HTTPS.new(uri.host, uri.inferred_port) : Net::HTTP.new(uri.host, uri.inferred_port))
53
+
54
+ case method
55
+ when :delete
56
+ # puts "in delete: #{uri.to_s}"
57
+ request = Net::HTTP::Delete.new(uri.to_s, @update_header)
58
+ when :get
59
+ # puts "in get: #{uri.to_s}"
60
+ request = Net::HTTP::Get.new(uri.to_s, @headers)
61
+ when :post_form
62
+ # puts "in post_form: #{uri.to_s}"
63
+ request = Net::HTTP::Post.new(uri.to_s, @headers)
64
+ request.set_form_data(content)
65
+ when :post
66
+ # puts "in post: #{uri.to_s}\n#{content}"
67
+ request = Net::HTTP::Post.new(uri.to_s, @headers)
68
+ request.body = content
69
+ when :put
70
+ # puts "in put: #{uri.to_s}\n#{content}"
71
+ request = Net::HTTP::Put.new(uri.to_s, @update_header)
72
+ request.body = content
73
+ end
74
+
75
+ response = http.request(request)
76
+
77
+ # recurse if necessary.
78
+ if response.kind_of? Net::HTTPRedirection
79
+ response = send(Addressable::URI.parse(response['location']), method, content, redirect_count - 1)
80
+ end
81
+
82
+ # Raise the appropiate error if necessary.
83
+ if response.kind_of? Net::HTTPForbidden
84
+ raise HTTPAuthorizationFailed, response.body
85
+
86
+ elsif response.kind_of? Net::HTTPBadRequest
87
+ raise HTTPRequestFailed, response.body
88
+
89
+ elsif response.kind_of? Net::HTTPNotFound
90
+ raise HTTPNotFound, response.body
91
+ end
92
+
93
+ return response
94
+ end
95
+
96
+ protected
97
+
98
+ def set_session_if_necessary(uri) #:nodoc:
99
+ # only extract the session if we don't already have one.
100
+ @session_id = uri.query_values['gsessionid'] if @session_id == nil && uri.query
101
+
102
+ if @session_id
103
+ uri.query ||= ''
104
+ uri.query_values = uri.query_values.merge({'gsessionid' => @session_id})
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ end