em-zimbreasy 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in em-zimbreasy.gemspec
4
+ gemspec
5
+ gem 'savon', :git => 'git://github.com/rankin/savon.git'
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Christopher Rankin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,86 @@
1
+ # Em::Zimbreasy
2
+
3
+ 2007!
4
+
5
+ Crankin will get that joke, he is to be thanked for assistance on this.
6
+
7
+ This is an open sourced wrapper for the Zimbra API. I only added functionality for CRUD on Calender Appointments,
8
+ because that's all I needed for the job I'm getting done. However, the code already contained within the libraries for calendars
9
+ and the structure make this an easily extensible gem if you need it for other purposes.
10
+
11
+ The API documentation I am using is located at
12
+
13
+ http://files.zimbra.com/docs/soap_api/8.0/soapapi-zimbra-doc/api-reference/index.html
14
+
15
+ Everybody who's interested in finally wrapping Zimbra well for Ruby, please download the gem and submit pull requests
16
+ with more of the methods on this API built out(there are over 100.)
17
+
18
+ This gem is still in it's infancy and has little to no error handling! I appreciate all the help I can get on this project, Zimbra is big!
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ gem 'em-zimbreasy'
25
+
26
+ And then execute:
27
+
28
+ $ bundle
29
+
30
+ Or install it yourself as:
31
+
32
+ $ gem install em-zimbreasy
33
+
34
+ ## Usage
35
+
36
+ It's pretty simple to use, I will post some examples:
37
+
38
+ zs = Em::Zimbreasy::Account.new('username', 'password', "https://yourzimbraserver.com/service/soap"); #login
39
+ zm = Em::Zimbreasy::Mail.new(zs); #create a Zimbreasy::Mail object. This has methods for the ZimbraMail submodule of their API.
40
+ z = zm.create_appointment({
41
+ :appointee_email => "neo@matrix.com",
42
+ :start_time => Time.now+1.days,
43
+ :end_time => (Time.now+1.days+1.hours),
44
+ :name => "Joss Whedon Meeting",
45
+ :subject => "Hallelujah",
46
+ :desc => "Ridiculous stuff happening here"
47
+ }) #I create an appointment.
48
+
49
+ => "BEGIN:VCALENDAR\r\nCALSCALE:GREGORIAN\r\nPRODID:iCalendar-Ruby\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\nDESCRIPTION:Poopmaster\r\nDTEND:20130208T171612\r\nDTSTAMP:20130207T161614\r\nDTSTART:20130208T161612\r\nCLASS:PRIVATE\r\nSEQUENCE:0\r\nSUMMARY:Jossss\r\nUID:336-335\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"
50
+
51
+
52
+ create_appointment returns ics formatted data, for use with icalendar. Notice that the UID in the data is 336-335. This is an invitation id, not an appointment id.
53
+ The appointment id is 336, the first part of it.
54
+
55
+ The other methods of note:
56
+
57
+ zm.get_appointment(336) #takes appt id
58
+
59
+ zm.modify_appointment({
60
+ :appointee_email => "neo@matrix.com",
61
+ :start_time => Time.now,
62
+ :end_time => (Time.now+2.hours),
63
+ :name => "Joss Whedon!!!",
64
+ :subject => "Hallelujah 2: The Electric Boogaloo",
65
+ :desc => "Yoda Fights Back", :inv_id => "336-335"
66
+ })
67
+
68
+ zm.cancel_appointment("336-335")
69
+
70
+ You'll notice get_appointment uses an actual appointment id, whereas modify appt and cancel appt need inv ids. I don't know why this is,
71
+ it seems Zimbra API only works with invitation, not appointment ids, when it comes to these methods. Get just needs appt ids. The final method of note is
72
+
73
+ zm.get_appt_summaries(Time.now-1.days, Time.now)
74
+
75
+ This just returns appointment Ics texts in an array. First arg is a Time object representing start date, second arg is end date.
76
+
77
+ ## Contributing
78
+
79
+ It'd be great if someone could write tests for these methods, I haven't had the time. If you want to write tests,
80
+ please do!
81
+
82
+ 1. Fork it
83
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
84
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
85
+ 4. Push to the branch (`git push origin my-new-feature`)
86
+ 5. Create new Pull Request
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/em-zimbreasy/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Jordan Prince", "Christopher Rankin"]
6
+ gem.email = ["crankin@pangeaequity.com"]
7
+ gem.description = %q{A no-nonsense async gem for the nonsensical Zimbra API.}
8
+ gem.summary = %q{A no-nonsense async gem for the nonsensical Zimbra API.}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "em-zimbreasy"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Em::Zimbreasy::VERSION
17
+
18
+ gem.add_dependency 'test-unit'
19
+ gem.add_dependency 'em-http-request'
20
+ gem.add_dependency 'em-synchrony'
21
+ gem.add_dependency 'nokogiri'
22
+ gem.add_dependency 'icalendar'
23
+ end
@@ -0,0 +1,18 @@
1
+ require "icalendar"
2
+ require "exceptions/zimbreasy_timeout_exception"
3
+ require "em-zimbreasy/version"
4
+ require "em-zimbreasy/mail"
5
+ require "em-zimbreasy/account"
6
+ require "savon"
7
+
8
+ module Em
9
+ module Zimbreasy
10
+
11
+ #takes a Time object. outputs string for zimbra api calls.
12
+ def self.zimbra_date(time)
13
+ time.strftime("%Y%m%dT%H%M%S")
14
+ end
15
+
16
+
17
+ end
18
+ end
@@ -0,0 +1,68 @@
1
+ require 'timeout'
2
+
3
+ module Em
4
+ module Zimbreasy
5
+ class Account
6
+ attr_accessor :user, :pass, :endpoint, :client, :soap_namespace, :zimbra_namespace
7
+
8
+ def initialize(user, pass, endpoint, adapter)
9
+ HTTPI::Adapter.use = adapter
10
+ @user = user
11
+ @pass = pass
12
+ @endpoint = endpoint
13
+ @soap_namespace = {
14
+ "xmlns:soap" => "http://schemas.xmlsoap.org/soap/envelope/"
15
+ }
16
+ @zimbra_namespace = "urn:zimbraAccount"
17
+ auth_request
18
+ end
19
+
20
+ def auth_request
21
+ response = make_call(
22
+ :AuthRequest,
23
+ { :persistAuthTokenCookie => 1, :xmlns => @zimbra_namespace },
24
+ {
25
+ :account => @user,
26
+ :password => @pass,
27
+ :session => "",
28
+ :attributes! => { :account => { :by => "name" } }
29
+ }
30
+ )
31
+ @client = Savon.client(
32
+ namespace_identifier: :none,
33
+ pretty_print_xml: true,
34
+ log: false,
35
+ endpoint: @endpoint,
36
+ namespace: soap_namespace,
37
+ convert_request_keys_to: :none,
38
+ headers: { "Cookie" => response.http.headers["set-cookie"].split(";").first }
39
+ )
40
+ end
41
+
42
+ def make_call(method, attrs={}, message)
43
+ tries = 0
44
+ soap_namespace = @soap_namespace
45
+ #@soap_namespace is undefined inside client.request, is not this obj. So we define it here.
46
+
47
+ @client ||= Savon.client(
48
+ namespace_identifier: :none,
49
+ pretty_print_xml: true,
50
+ log: false,
51
+ endpoint: @endpoint,
52
+ namespace: soap_namespace,
53
+ convert_request_keys_to: :none
54
+ )
55
+ response = @client.call(method, attributes: attrs, message: message, soap_action: @zimbra_namespace)
56
+
57
+ rescue Timeout::Error => e
58
+ pp "Retrying"
59
+ tries+=1
60
+ if tries >= 4
61
+ throw ZimbreasyTimeoutException.new
62
+ else
63
+ retry
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,250 @@
1
+ module Em
2
+ module Zimbreasy
3
+ class Mail
4
+ include Icalendar
5
+ attr_accessor :account, :zimbra_namespace
6
+
7
+ def initialize(account)
8
+ @account = account
9
+ @zimbra_namespace = "urn:zimbraMail"
10
+ end
11
+
12
+ #Params can contain the following:
13
+ #:appointee_emails(req)
14
+ #:start_time(opt)
15
+ #:end_time(opt)
16
+ #:name(opt)
17
+ #:subject(opt)
18
+ #:desc(opt)
19
+ #:mime_type(opt)
20
+ #:is_org(opt) boolean is organizer or not
21
+ #:or -> organizer email
22
+ #Returns Appt's Inv id as UID in an i_cal text.. Is normally a number like 140-141,
23
+ #the first number is the appt id, which you need for getting.
24
+ def create_appointment(params)
25
+ params[:start_time] = Em::Zimbreasy.zimbra_date(params[:start_time]) if params[:start_time]
26
+ params[:end_time] = Em::Zimbreasy.zimbra_date(params[:end_time]) if params[:end_time]
27
+ response = account.make_call(
28
+ :CreateAppointmentRequest,
29
+ { "xmlns" => @zimbra_namespace, "echo" => (params[:echo] || "0")},
30
+ appointment_hash(params)
31
+ )
32
+ params.merge!({:appt_id => response.body[:create_appointment_response][:@inv_id]})
33
+
34
+ to_ical(params)
35
+ end
36
+
37
+ def get_free_busy(start_time, end_time, email)
38
+ start_time = start_time.to_i*1000 #it wants millis, to_i gives seconds.
39
+ end_time = end_time.to_i*1000
40
+
41
+ response = account.make_call(
42
+ :GetFreeBusyRequest,
43
+ {:xmlns => @zimbra_namespace, :s => start_time, :e => end_time},
44
+ { :usr => { :name => email }}
45
+ )
46
+
47
+ array = []
48
+ return response.body[:get_free_busy_response][:usr].reject { |k,v| k if k == :@id }.inject(Hash.new) do |hash, entry|
49
+ if entry[1].is_a?(Array)
50
+ array_of_times = entry[1].inject(Array.new) do |times_array, times_entry|
51
+ times_array << { :s => Time.at(times_entry[:@s].to_f/1000.0), :e => Time.at(times_entry[:@e].to_f/1000.0) }
52
+ times_array
53
+ end
54
+ hash[entry[0]] = array_of_times
55
+ else
56
+ hash[entry[0]] = [ {:s => Time.at(entry[1][:@s].to_f/1000.0), :e => Time.at(entry[1][:@e].to_f/1000.0)} ]
57
+ end
58
+ hash
59
+ end
60
+ end
61
+
62
+ def get_appointment(appt_id)
63
+
64
+ response = account.make_call(
65
+ :GetAppointmentRequest,
66
+ { :xmlns => @zimbra_namespace, :id => appt_id},
67
+ {}
68
+ )
69
+
70
+ comp = response.body[:get_appointment_response][:appt][:inv][:comp]
71
+
72
+ hash = {
73
+ :start_time => comp[:s][:@d],
74
+ :end_time => comp[:e][:@d],
75
+ :desc => comp[:desc],
76
+ :name => comp[:@name],
77
+ :appt_id => appt_id
78
+ }
79
+
80
+ to_ical(hash)
81
+ end
82
+
83
+ def get_appt_summaries(start_date, end_date)
84
+ start_date = start_date.to_i*1000 #it wants millis, to_i gives seconds.
85
+ end_date = end_date.to_i*1000
86
+
87
+ response = account.make_call(
88
+ :GetApptSummariesRequest,
89
+ { :xmlns => @zimbra_namespace, :e => end_date, :s => start_date},
90
+ {}
91
+ )
92
+
93
+ return [] if response.body[:get_appt_summaries_response][:appt].nil?
94
+
95
+ appts = []
96
+
97
+ if response.body[:get_appt_summaries_response][:appt].is_a?(Array)
98
+ response.body[:get_appt_summaries_response][:appt].each do |appt|
99
+
100
+ inst = appt[:inst]
101
+
102
+ hash = {
103
+ :start_time => Em::Zimbreasy.zimbra_date(Time.at(inst[:@s].to_f/1000.0)),
104
+ :name => appt[:@name],
105
+ :appt_id => appt[:@id]
106
+ }
107
+
108
+ appts << to_ical(hash)
109
+ end
110
+ else
111
+ appt = response.body[:get_appt_summaries_response][:appt]
112
+
113
+ inst = appt[:inst]
114
+
115
+ hash = {
116
+ :start_time => Zimbreasy.zimbra_date(Time.at(inst[:@s].to_f/1000.0)),
117
+ :name => appt[:@name],
118
+ :appt_id => appt[:@id]
119
+ }
120
+
121
+ appts << to_ical(hash)
122
+ end
123
+
124
+ appts
125
+ end
126
+
127
+ #same param options as create_appointment, but you can add :inv_id too.
128
+ def modify_appointment(params)
129
+ params[:start_time] = Em::Zimbreasy.zimbra_date(params[:start_time]) if params[:start_time]
130
+ params[:end_time] = Em::Zimbreasy.zimbra_date(params[:end_time]) if params[:end_time]
131
+
132
+ response = account.make_call(
133
+ :ModifyAppointmentRequest,
134
+ { :xmlns => @zimbra_namespace, :id => params[:inv_id]},
135
+ appointment_hash(params)
136
+ )
137
+
138
+ to_ical(params.merge({:appt_id => params[:inv_id]}))
139
+ end
140
+
141
+ #returns true if it worked, inv_id is not appt_id, it's normally something like 320-319, the first number is appt_id.
142
+ def cancel_appointment(inv_id, emails, subject=nil, content=nil)
143
+ unless inv_id and inv_id.is_a?(String) and inv_id.match(/-/) and inv_id.split("-").count==2 #so it has x-y formatting.
144
+ raise 'inv_id must be string of format x-y, where x and y are numbers.'
145
+ end
146
+
147
+ message = {
148
+ :m => {
149
+ :su => subject,
150
+ :mp => {
151
+ :content => content,
152
+ :@ct => "text/plain"
153
+ },
154
+ :attributes! => { :e => { :a => [], :t => [] } }
155
+ }
156
+ }
157
+
158
+ message[:m][:e] = []
159
+ emails.each do |email|
160
+ message[:m][:e] << ""
161
+ message[:m][:attributes!][:e][:a].push(email)
162
+ message[:m][:attributes!][:e][:t].push("t")
163
+ end
164
+
165
+ response = account.make_call(
166
+ :CancelAppointmentRequest,
167
+ { :xmlns => @zimbra_namespace, :id => inv_id, :comp => 0},
168
+ message
169
+ )
170
+
171
+ return !response.body[:cancel_appointment_response].nil?
172
+ end
173
+
174
+ private
175
+
176
+ def to_ical(params)
177
+ calendar = Calendar.new
178
+ calendar.event do
179
+ dtstart params[:start_time]
180
+ dtend params[:end_time]
181
+ summary params[:name]
182
+ description params[:desc]
183
+ uid params[:appt_id]
184
+ klass "PRIVATE"
185
+ end
186
+ calendar.to_ical
187
+ end
188
+
189
+ def appointment_hash(params)
190
+ message = {
191
+ :m => {
192
+ :mp => { :ct => (params[:mime_type] || "text/plain") },
193
+ :inv => {
194
+ :mp => { :ct =>(params[:mime_type] || "text/plain") },
195
+ :desc => params[:desc],
196
+ :comment => params[:desc],
197
+ :@rsvp => "0",
198
+ :@compNum => "0",
199
+ :@method => "none",
200
+ :@name => params[:name],
201
+ :@isOrg => params[:is_org],
202
+ :@noBlob => 1
203
+ },
204
+ :@su => params[:subject],
205
+ :attributes! => { :e => { :a => [], :t => [] } }
206
+ }
207
+ }
208
+
209
+ message[:m][:inv][:s] = { :d => params[:start_time], :tz => params[:tz] } if params[:start_time]
210
+ message[:m][:inv][:e] = { :d => params[:end_time], :tz => params[:tz] } if params[:end_time]
211
+ message[:m][:inv][:fb] = params[:fb] if params[:fb]
212
+ message[:m][:inv][:fba] = params[:fba] if params[:fba]
213
+
214
+ message[:m][:inv][:comp] = {
215
+ :s => { :d => params[:start_time], :tz => params[:tz] },
216
+ :desc => params[:desc],
217
+ :@noBlob => 1,
218
+ :@method => "none", :@compNum => 1, :@rsvp => "0", :@isOrg => params[:is_org],
219
+ :attributes! => { :at => { :a => [], :rsvp => [], :role => [], :ptst => [], :cutype => [] } }
220
+ }
221
+
222
+ message[:m][:inv][:comp][:@fb] = params[:fb] if params[:fb]
223
+ message[:m][:inv][:comp][:@fb] = params[:fba] if params[:fba]
224
+
225
+ message[:m][:inv][:comp][:e] = { :d => params[:end_time], :tz => params[:tz] }
226
+
227
+ message[:m][:inv][:comp][:or] = { :@a => params[:or] }
228
+ message[:m][:inv][:or] = { :@a => params[:or] } #set organizer
229
+
230
+ message[:m][:e] = []
231
+ message[:m][:inv][:comp][:at] = []
232
+ params[:appointee_emails].each do |email|
233
+
234
+ message[:m][:e] << ""
235
+ message[:m][:attributes!][:e][:a].push(email) #set attendees here
236
+ message[:m][:attributes!][:e][:t].push("t")
237
+
238
+ message[:m][:inv][:comp][:at] << ""
239
+ message[:m][:inv][:comp][:attributes!][:at][:a].push(email) # also set attendees here
240
+ message[:m][:inv][:comp][:attributes!][:at][:rsvp].push("1")
241
+ message[:m][:inv][:comp][:attributes!][:at][:role].push("REQ")
242
+ message[:m][:inv][:comp][:attributes!][:at][:ptst].push("AC")
243
+ message[:m][:inv][:comp][:attributes!][:at][:cutype].push("IND")
244
+ end
245
+ return message
246
+ end
247
+
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,5 @@
1
+ module Em
2
+ module Zimbreasy
3
+ VERSION = "0.0.7"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class ZimbreasyTimeoutException < Exception
2
+ def message
3
+ 'Request timed out after multiple retries.'
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-zimbreasy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jordan Prince
9
+ - Christopher Rankin
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-10-17 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: test-unit
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: em-http-request
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: em-synchrony
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: nokogiri
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ - !ruby/object:Gem::Dependency
80
+ name: icalendar
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :runtime
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ description: A no-nonsense async gem for the nonsensical Zimbra API.
96
+ email:
97
+ - crankin@pangeaequity.com
98
+ executables: []
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - .gitignore
103
+ - Gemfile
104
+ - LICENSE
105
+ - README.md
106
+ - Rakefile
107
+ - em-zimbreasy.gemspec
108
+ - lib/em-zimbreasy.rb
109
+ - lib/em-zimbreasy/account.rb
110
+ - lib/em-zimbreasy/mail.rb
111
+ - lib/em-zimbreasy/version.rb
112
+ - lib/exceptions/zimbreasy_timeout_exception.rb
113
+ homepage: ''
114
+ licenses: []
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 1.8.23
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: A no-nonsense async gem for the nonsensical Zimbra API.
137
+ test_files: []