sonar_ews_pull_connector 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/lib/ews_pull_connector/ews_pull_connector.rb +208 -0
- data/lib/sonar_ews_pull_connector.rb +1 -0
- data/spec/ews_pull_connector/ews_pull_connector_spec.rb +490 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +12 -0
- metadata +207 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Trampoline Systems Ltd
|
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,17 @@
|
|
1
|
+
= sonar-ews-pull-connector
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2011 Trampoline Systems Ltd. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "sonar_ews_pull_connector"
|
8
|
+
gem.summary = %Q{Exchange Web Services connector for Exchange 2007/2010}
|
9
|
+
gem.description = %Q{A sonar-connector for extracting emails from Exchange 2007/2010 through Exchange Web Services}
|
10
|
+
gem.email = "craig@trampolinesystems.com"
|
11
|
+
gem.homepage = "http://github.com/trampoline/sonar-ews-pull-connector"
|
12
|
+
gem.authors = ["mccraigmccraig"]
|
13
|
+
gem.add_dependency "sonar_connector", ">= 0.7.2"
|
14
|
+
gem.add_dependency "savon", ">= 0.8.6"
|
15
|
+
gem.add_dependency "ntlm-http", ">= 0.1.1"
|
16
|
+
gem.add_dependency "httpclient", ">= 2.1.6.1.1"
|
17
|
+
gem.add_dependency "fetch_in", ">= 0.2.0"
|
18
|
+
gem.add_dependency "rfc822_util", ">= 0.1.1"
|
19
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
20
|
+
gem.add_development_dependency "rr", ">= 0.10.5"
|
21
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
22
|
+
end
|
23
|
+
Jeweler::GemcutterTasks.new
|
24
|
+
rescue LoadError
|
25
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'spec/rake/spectask'
|
29
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
30
|
+
spec.libs << 'lib' << 'spec'
|
31
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
35
|
+
spec.libs << 'lib' << 'spec'
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
37
|
+
spec.rcov = true
|
38
|
+
end
|
39
|
+
|
40
|
+
task :spec => :check_dependencies
|
41
|
+
|
42
|
+
task :default => :spec
|
43
|
+
|
44
|
+
require 'rake/rdoctask'
|
45
|
+
Rake::RDocTask.new do |rdoc|
|
46
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
47
|
+
|
48
|
+
rdoc.rdoc_dir = 'rdoc'
|
49
|
+
rdoc.title = "sonar-ews-pull-connector #{version}"
|
50
|
+
rdoc.rdoc_files.include('README*')
|
51
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
52
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'json'
|
3
|
+
require 'sonar_connector'
|
4
|
+
require 'rfc822_util'
|
5
|
+
require 'base64'
|
6
|
+
require 'md5'
|
7
|
+
require 'rews'
|
8
|
+
|
9
|
+
module Sonar
|
10
|
+
module Connector
|
11
|
+
class EwsPullConnector < Sonar::Connector::Base
|
12
|
+
|
13
|
+
MIN_BATCH_SIZE = 2
|
14
|
+
DEFAULT_BATCH_SIZE = 100
|
15
|
+
|
16
|
+
attr_accessor :url
|
17
|
+
attr_accessor :auth
|
18
|
+
attr_accessor :user
|
19
|
+
attr_accessor :password
|
20
|
+
attr_accessor :distinguished_folders
|
21
|
+
attr_accessor :batch_size
|
22
|
+
attr_accessor :delete
|
23
|
+
attr_accessor :is_journal
|
24
|
+
|
25
|
+
def parse(settings)
|
26
|
+
["name", "repeat_delay", "url", "auth", "user", "password", "distinguished_folders", "batch_size"].each do |param|
|
27
|
+
raise Sonar::Connector::InvalidConfig.new("#{self.class}: param '#{param}' is blank") if settings[param].blank?
|
28
|
+
end
|
29
|
+
|
30
|
+
@url = settings["url"]
|
31
|
+
@auth = settings["auth"]
|
32
|
+
@user = settings["user"]
|
33
|
+
@password = settings["password"]
|
34
|
+
@mailbox_email = settings["mailbox_email"]
|
35
|
+
@distinguished_folders = settings["distinguished_folders"]
|
36
|
+
@batch_size = [settings["batch_size"] || DEFAULT_BATCH_SIZE, MIN_BATCH_SIZE].max
|
37
|
+
@delete = !!settings["delete"]
|
38
|
+
@is_journal = !!settings["is_journal"]
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
"#<#{self.class} @url=#{url}, @auth=#{auth}, @user=#{user}, @password=#{password}, @distinguished_folders=#{distinguished_folders}, @batch_size=#{batch_size}, @delete=#{delete}, @is_journal=#{is_journal}>"
|
43
|
+
end
|
44
|
+
|
45
|
+
def distinguished_folder_ids
|
46
|
+
return @distinguished_folder_ids if @distinguished_folder_ids
|
47
|
+
client ||= Rews::Client.new(url, auth, user, password)
|
48
|
+
|
49
|
+
@distinguished_folder_ids = @distinguished_folders.inject([]) do |ids, (name, mailbox_email)|
|
50
|
+
ids << client.distinguished_folder_id(name, mailbox_email)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# find message ids from a folder
|
55
|
+
def find(folder_id, offset)
|
56
|
+
find_opts = {
|
57
|
+
:sort_order=>[["item:DateTimeReceived", "Ascending"]],
|
58
|
+
:indexed_page_item_view=>{
|
59
|
+
:max_entries_returned=>batch_size,
|
60
|
+
:offset=>offset},
|
61
|
+
:item_shape=>{
|
62
|
+
:base_shape=>:IdOnly}}
|
63
|
+
|
64
|
+
restriction = [:==, "item:ItemClass", "IPM.Note"]
|
65
|
+
if state[folder_id.key]
|
66
|
+
restriction = [:and,
|
67
|
+
restriction,
|
68
|
+
[:>= , "item:DateTimeReceived", state[folder_id.key]]]
|
69
|
+
end
|
70
|
+
find_opts[:restriction] = restriction
|
71
|
+
|
72
|
+
folder_id.find_item(find_opts)
|
73
|
+
end
|
74
|
+
|
75
|
+
def get(folder_id, msg_ids)
|
76
|
+
get_opts = {
|
77
|
+
:item_shape=>{
|
78
|
+
:base_shape=>:IdOnly,
|
79
|
+
:additional_properties=>[[:field_uri, "item:ItemClass"],
|
80
|
+
[:field_uri, "item:DateTimeSent"],
|
81
|
+
[:field_uri, "item:DateTimeReceived"],
|
82
|
+
[:field_uri, "item:InReplyTo"],
|
83
|
+
[:field_uri, "message:InternetMessageId"],
|
84
|
+
[:field_uri, "message:References"],
|
85
|
+
[:field_uri, "message:From"],
|
86
|
+
[:field_uri, "message:Sender"],
|
87
|
+
[:field_uri, "message:ToRecipients"],
|
88
|
+
[:field_uri, "message:CcRecipients"],
|
89
|
+
[:field_uri, "message:BccRecipients"]]}}
|
90
|
+
|
91
|
+
# we have to retrieve the journal message content and unwrap the
|
92
|
+
# original message if this
|
93
|
+
# is an exchange journal mailbox
|
94
|
+
if is_journal
|
95
|
+
get_opts[:item_shape][:additional_properties] << [:field_uri, "item:MimeContent"]
|
96
|
+
end
|
97
|
+
|
98
|
+
folder_id.get_item(msg_ids, get_opts)
|
99
|
+
end
|
100
|
+
|
101
|
+
def action
|
102
|
+
distinguished_folder_ids.each do |fid|
|
103
|
+
log.info "processing: #{fid.inspect}"
|
104
|
+
|
105
|
+
offset = 0
|
106
|
+
|
107
|
+
begin
|
108
|
+
msg_ids = find(fid, offset)
|
109
|
+
|
110
|
+
if msg_ids && msg_ids.length>0
|
111
|
+
msgs = get(fid, msg_ids)
|
112
|
+
|
113
|
+
# if there is no state, then state is set to the first message timestamp
|
114
|
+
state[fid.key] ||= msgs.first[:date_time_received].to_s if msgs.first[:date_time_received]
|
115
|
+
|
116
|
+
save_messages(msgs)
|
117
|
+
|
118
|
+
if msgs.last[:date_time_received] != state[fid.key]
|
119
|
+
finished=true
|
120
|
+
state[fid.key] = msgs.last[:date_time_received].to_s
|
121
|
+
end
|
122
|
+
|
123
|
+
delete_messages(fid, msgs) if delete
|
124
|
+
|
125
|
+
offset += msg_ids.length
|
126
|
+
end
|
127
|
+
end while msg_ids.length>0 && !finished
|
128
|
+
|
129
|
+
save_state
|
130
|
+
|
131
|
+
log.info "finished processing: #{fid.inspect}"
|
132
|
+
end
|
133
|
+
log.info "finished action"
|
134
|
+
end
|
135
|
+
|
136
|
+
def save_messages(messages)
|
137
|
+
messages.each do |msg|
|
138
|
+
h = if is_journal
|
139
|
+
extract_journalled_message(msg)
|
140
|
+
else
|
141
|
+
message_to_hash(msg)
|
142
|
+
end
|
143
|
+
|
144
|
+
if !h
|
145
|
+
log.warn("no data extracted from message. could be a decoding eror")
|
146
|
+
return
|
147
|
+
end
|
148
|
+
|
149
|
+
h[:type] = "email"
|
150
|
+
h[:connector] = name
|
151
|
+
h[:source] = url
|
152
|
+
h[:source_id]=msg[:item_id][:id]
|
153
|
+
h[:received_at] = msg[:date_time_received]
|
154
|
+
|
155
|
+
|
156
|
+
fname = MD5.hexdigest(msg[:item_id][:id])
|
157
|
+
filestore.write(:complete, "#{fname}.json", h.to_json)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def mailbox_to_hash(mailbox)
|
162
|
+
[:name, :email_address].inject({}) do |h, k|
|
163
|
+
h[k] = mailbox[k]
|
164
|
+
h
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def mailbox_recipients_to_hashes(recipients)
|
169
|
+
mailboxes = recipients[:mailbox] if recipients
|
170
|
+
mailboxes = [mailboxes] if !mailboxes.is_a?(Array)
|
171
|
+
mailboxes.compact.map{|a| mailbox_to_hash(a)}
|
172
|
+
end
|
173
|
+
|
174
|
+
def message_to_hash(msg)
|
175
|
+
message_id = Rfc822Util.strip_header(msg[:internet_message_id]) if msg[:internet_message_id]
|
176
|
+
in_reply_to = Rfc822Util.strip_headers(msg[:in_reply_to]).first if msg[:in_reply_to]
|
177
|
+
references = Rfc822Util.strip_headers(msg[:references]) if msg[:references]
|
178
|
+
|
179
|
+
json_hash = {
|
180
|
+
:message_id=>message_id,
|
181
|
+
:sent_at=>msg[:date_time_sent].to_s,
|
182
|
+
:in_reply_to=>in_reply_to,
|
183
|
+
:references=>references,
|
184
|
+
:from=>mailbox_recipients_to_hashes(msg[:from]).first,
|
185
|
+
:sender=>mailbox_recipients_to_hashes(msg[:sender]).first,
|
186
|
+
:to=>mailbox_recipients_to_hashes(msg[:to_recipients]),
|
187
|
+
:cc=>mailbox_recipients_to_hashes(msg[:cc_recipients]),
|
188
|
+
:bcc=>mailbox_recipients_to_hashes(msg[:bcc_recipients])
|
189
|
+
}
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
def extract_journalled_message(message)
|
194
|
+
mime_msg = Base64::decode64(message[:mime_content])
|
195
|
+
journal_msg = Rfc822Util.extract_journalled_mail(mime_msg)
|
196
|
+
Rfc822Util.mail_to_hash(journal_msg)
|
197
|
+
rescue Exception=>e
|
198
|
+
log.warn("problem extracting journalled message from wrapper message")
|
199
|
+
log.warn(e)
|
200
|
+
end
|
201
|
+
|
202
|
+
def delete_messages(folder_id, messages)
|
203
|
+
log.info "deleting #{messages.length} messages from #{folder_id.inspect}"
|
204
|
+
folder_id.delete_item(messages, :delete_type=>:HardDelete)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path("../ews_pull_connector/ews_pull_connector", __FILE__)
|
@@ -0,0 +1,490 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
module Sonar
|
4
|
+
module Connector
|
5
|
+
describe EwsPullConnector do
|
6
|
+
|
7
|
+
before do
|
8
|
+
setup_valid_config_file
|
9
|
+
@base_config = Sonar::Connector::Config.load(valid_config_filename)
|
10
|
+
end
|
11
|
+
|
12
|
+
def one_folder_config
|
13
|
+
{
|
14
|
+
'name'=>'foobarcom-exchange',
|
15
|
+
'repeat_delay'=>60,
|
16
|
+
'url'=>"https://foo.com/EWS/Exchange.asmx",
|
17
|
+
'auth'=>'ntlm',
|
18
|
+
'user'=>"foo",
|
19
|
+
'password'=>"foopass",
|
20
|
+
'distinguished_folders'=>[["inbox", "foo@foo.com"]],
|
21
|
+
'batch_size'=>100,
|
22
|
+
'delete'=>true
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def two_folder_config
|
27
|
+
{
|
28
|
+
'name'=>'foobarcom-exchange',
|
29
|
+
'repeat_delay'=>60,
|
30
|
+
'url'=>"https://foo.com/EWS/Exchange.asmx",
|
31
|
+
'auth'=>'ntlm',
|
32
|
+
'user'=>"foo",
|
33
|
+
'password'=>"foopass",
|
34
|
+
'distinguished_folders'=>[["inbox", "foo@foo.com"], "inbox"],
|
35
|
+
'batch_size'=>100,
|
36
|
+
'delete'=>true
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should parse config" do
|
41
|
+
Sonar::Connector::EwsPullConnector.new(two_folder_config, @base_config)
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "distinguished_folder_ids" do
|
45
|
+
it "should create Rews::Clients for each configured distinguished folder" do
|
46
|
+
c=Sonar::Connector::EwsPullConnector.new(two_folder_config, @base_config)
|
47
|
+
fids = c.distinguished_folder_ids
|
48
|
+
fids.size.should == 2
|
49
|
+
|
50
|
+
fids[0].client.should be(fids[1].client)
|
51
|
+
client = fids[0].client
|
52
|
+
client.endpoint.should == "https://foo.com/EWS/Exchange.asmx"
|
53
|
+
client.auth_type.should == "ntlm"
|
54
|
+
client.user.should == 'foo'
|
55
|
+
client.password.should == 'foopass'
|
56
|
+
|
57
|
+
fid0 = fids[0]
|
58
|
+
fid0.id.should == 'inbox'
|
59
|
+
fid0.mailbox_email.should == 'foo@foo.com'
|
60
|
+
fid0.key.should == ['distinguished_folder', 'inbox', 'foo@foo.com']
|
61
|
+
|
62
|
+
fid1 = fids[1]
|
63
|
+
fid1.id.should == 'inbox'
|
64
|
+
fid1.mailbox_email.should == nil
|
65
|
+
fid1.key.should == ['distinguished_folder', 'inbox']
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should cache the Rews::Clients" do
|
69
|
+
c=Sonar::Connector::EwsPullConnector.new(two_folder_config, @base_config)
|
70
|
+
fids = c.distinguished_folder_ids
|
71
|
+
fid0 = fids[0]
|
72
|
+
fid1 = fids[1]
|
73
|
+
|
74
|
+
fids = c.distinguished_folder_ids
|
75
|
+
fid0.should be(fids[0])
|
76
|
+
fid1.should be(fids[1])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "find" do
|
81
|
+
it "should include batch_size and offset but not item:DateTimeReceived restriction if there is no folder state" do
|
82
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
83
|
+
state={}
|
84
|
+
stub(c.state){state}
|
85
|
+
stub(c).batch_size{17}
|
86
|
+
|
87
|
+
folder_id = Object.new
|
88
|
+
folder_key = Object.new
|
89
|
+
stub(folder_id).key{folder_key}
|
90
|
+
|
91
|
+
mock(folder_id).find_item.with_any_args do |find_opts|
|
92
|
+
find_opts.should == {
|
93
|
+
:sort_order=>[["item:DateTimeReceived", "Ascending"]],
|
94
|
+
:indexed_page_item_view=>{
|
95
|
+
:max_entries_returned=>17,
|
96
|
+
:offset=>123},
|
97
|
+
:item_shape=>{
|
98
|
+
:base_shape=>:IdOnly},
|
99
|
+
:restriction=>[:==, "item:ItemClass", "IPM.Note"]}
|
100
|
+
end
|
101
|
+
|
102
|
+
c.find(folder_id, 123)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should include a item:DateTimeReceived restriction if there is folder state" do
|
106
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
107
|
+
stub(c).batch_size{17}
|
108
|
+
|
109
|
+
folder_id = Object.new
|
110
|
+
folder_key = Object.new
|
111
|
+
stub(folder_id).key{folder_key}
|
112
|
+
|
113
|
+
state_time = DateTime.now.to_s
|
114
|
+
stub(c).state.stub!.[](folder_key){state_time}
|
115
|
+
|
116
|
+
mock(folder_id).find_item.with_any_args do |find_opts|
|
117
|
+
find_opts.should == {
|
118
|
+
:sort_order=>[["item:DateTimeReceived", "Ascending"]],
|
119
|
+
:indexed_page_item_view=>{
|
120
|
+
:max_entries_returned=>17,
|
121
|
+
:offset=>123},
|
122
|
+
:item_shape=>{
|
123
|
+
:base_shape=>:IdOnly},
|
124
|
+
:restriction=>[:and,
|
125
|
+
[:==, "item:ItemClass", "IPM.Note"],
|
126
|
+
[:>=, "item:DateTimeReceived", state_time]]}
|
127
|
+
end
|
128
|
+
|
129
|
+
c.find(folder_id, 123)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "get" do
|
134
|
+
it "should not fetch message content if !is_journal" do
|
135
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
136
|
+
|
137
|
+
folder_id = Object.new
|
138
|
+
msg_ids = Object.new
|
139
|
+
|
140
|
+
mock(folder_id).get_item.with_any_args do |mids, get_opts|
|
141
|
+
mids.should be(msg_ids)
|
142
|
+
get_opts.should == {
|
143
|
+
:item_shape=>{
|
144
|
+
:base_shape=>:IdOnly,
|
145
|
+
:additional_properties=>[[:field_uri, "item:ItemClass"],
|
146
|
+
[:field_uri, "item:DateTimeSent"],
|
147
|
+
[:field_uri, "item:DateTimeReceived"],
|
148
|
+
[:field_uri, "item:InReplyTo"],
|
149
|
+
[:field_uri, "message:InternetMessageId"],
|
150
|
+
[:field_uri, "message:References"],
|
151
|
+
[:field_uri, "message:From"],
|
152
|
+
[:field_uri, "message:Sender"],
|
153
|
+
[:field_uri, "message:ToRecipients"],
|
154
|
+
[:field_uri, "message:CcRecipients"],
|
155
|
+
[:field_uri, "message:BccRecipients"]]}}
|
156
|
+
end
|
157
|
+
|
158
|
+
c.get(folder_id, msg_ids)
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should fetch message content if is_journal" do
|
162
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
163
|
+
stub(c).is_journal{true}
|
164
|
+
|
165
|
+
folder_id = Object.new
|
166
|
+
msg_ids = Object.new
|
167
|
+
|
168
|
+
mock(folder_id).get_item.with_any_args do |mids, get_opts|
|
169
|
+
mids.should be(msg_ids)
|
170
|
+
get_opts.should == {
|
171
|
+
:item_shape=>{
|
172
|
+
:base_shape=>:IdOnly,
|
173
|
+
:additional_properties=>[[:field_uri, "item:ItemClass"],
|
174
|
+
[:field_uri, "item:DateTimeSent"],
|
175
|
+
[:field_uri, "item:DateTimeReceived"],
|
176
|
+
[:field_uri, "item:InReplyTo"],
|
177
|
+
[:field_uri, "message:InternetMessageId"],
|
178
|
+
[:field_uri, "message:References"],
|
179
|
+
[:field_uri, "message:From"],
|
180
|
+
[:field_uri, "message:Sender"],
|
181
|
+
[:field_uri, "message:ToRecipients"],
|
182
|
+
[:field_uri, "message:CcRecipients"],
|
183
|
+
[:field_uri, "message:BccRecipients"],
|
184
|
+
[:field_uri, "item:MimeContent"]]}}
|
185
|
+
end
|
186
|
+
|
187
|
+
c.get(folder_id, msg_ids)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe "action" do
|
192
|
+
it "should make a Rews find_item request, save, update state, delete" do
|
193
|
+
c=Sonar::Connector::EwsPullConnector.new(two_folder_config, @base_config)
|
194
|
+
state = {}
|
195
|
+
stub(c).state{state}
|
196
|
+
|
197
|
+
c.distinguished_folder_ids.each do |fid|
|
198
|
+
msg_ids = Object.new
|
199
|
+
stub(msg_ids).length{1}
|
200
|
+
|
201
|
+
msgs = Object.new
|
202
|
+
stub(msgs).first.stub!.[](:date_time_received){ DateTime.now-1 }
|
203
|
+
stub(msgs).last.stub!.[](:date_time_received){ DateTime.now }
|
204
|
+
|
205
|
+
mock(fid).find_item(anything){msg_ids}
|
206
|
+
mock(fid).get_item(msg_ids, anything){msgs}
|
207
|
+
|
208
|
+
mock(c).save_messages(msgs)
|
209
|
+
mock(c).delete_messages(fid, msgs)
|
210
|
+
end
|
211
|
+
c.action
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should have a single item:ItemClass Restriction clause if fstate is nil" do
|
215
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
216
|
+
fid = c.distinguished_folder_ids.first
|
217
|
+
|
218
|
+
msg_ids = Object.new
|
219
|
+
stub(msg_ids).length{1}
|
220
|
+
mock(fid).find_item(anything) do |query_opts|
|
221
|
+
r = query_opts[:restriction]
|
222
|
+
r.should == [:==, "item:ItemClass", "IPM.Note"]
|
223
|
+
msg_ids
|
224
|
+
end
|
225
|
+
|
226
|
+
msgs=Object.new
|
227
|
+
stub(msgs).first.stub!.[](:date_time_received){ DateTime.now-1 }
|
228
|
+
stub(msgs).last.stub!.[](:date_time_received){DateTime.now}
|
229
|
+
|
230
|
+
mock(fid).get_item(msg_ids, anything){msgs}
|
231
|
+
mock(c).save_messages(msgs)
|
232
|
+
mock(c).delete_messages(fid, msgs)
|
233
|
+
|
234
|
+
c.action
|
235
|
+
end
|
236
|
+
|
237
|
+
it "should have a second item:DateTimeReceived Restriction clause if fstate is non-nil" do
|
238
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
239
|
+
|
240
|
+
fid = c.distinguished_folder_ids.first
|
241
|
+
|
242
|
+
state_time = DateTime.now - 1
|
243
|
+
state = {fid.key => state_time.to_s}
|
244
|
+
stub(c).state{state}
|
245
|
+
|
246
|
+
msg_ids = Object.new
|
247
|
+
stub(msg_ids).length{1}
|
248
|
+
mock(fid).find_item(anything) do |query_opts|
|
249
|
+
r = query_opts[:restriction]
|
250
|
+
r[0].should == :and
|
251
|
+
r[1].should == [:==, "item:ItemClass", "IPM.Note"]
|
252
|
+
r[2].should == [:>=, "item:DateTimeReceived", state_time.to_s]
|
253
|
+
msg_ids
|
254
|
+
end
|
255
|
+
|
256
|
+
msgs = Object.new
|
257
|
+
stub(msgs).first.stub!.[](:date_time_received){state_time}
|
258
|
+
stub(msgs).last.stub!.[](:date_time_received){DateTime.now}
|
259
|
+
|
260
|
+
mock(fid).get_item(msg_ids, anything){msgs}
|
261
|
+
mock(c).save_messages(msgs)
|
262
|
+
mock(c).delete_messages(fid, msgs)
|
263
|
+
|
264
|
+
c.action
|
265
|
+
end
|
266
|
+
|
267
|
+
it "should cycle through messages with identical item:DateTimeReceived, so state is always updated even if 'delete' option is false" do
|
268
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
269
|
+
|
270
|
+
fid = c.distinguished_folder_ids.first
|
271
|
+
|
272
|
+
state_time = (DateTime.now - 2).to_s
|
273
|
+
state = {fid.key => state_time}
|
274
|
+
stub(c).state{state}
|
275
|
+
|
276
|
+
msg_ids = Object.new
|
277
|
+
stub(msg_ids).length{10}
|
278
|
+
msgs = Object.new
|
279
|
+
stub(msgs).first.stub!.[](:date_time_received){state_time}
|
280
|
+
stub(msgs).last.stub!.[](:date_time_received){state_time}
|
281
|
+
|
282
|
+
more_msg_ids = Object.new
|
283
|
+
stub(more_msg_ids).length{1}
|
284
|
+
more_msgs = Object.new
|
285
|
+
stub(more_msgs).first.stub!.[](:date_time_received){state_time}
|
286
|
+
stub(more_msgs).last.stub!.[](:date_time_received){DateTime.now}
|
287
|
+
|
288
|
+
mock(fid).find_item.with_any_args.twice do |opts|
|
289
|
+
if opts[:indexed_page_item_view][:offset]==0
|
290
|
+
msg_ids
|
291
|
+
elsif opts[:indexed_page_item_view][:offset]==10
|
292
|
+
more_msg_ids
|
293
|
+
else
|
294
|
+
raise "oops"
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
mock(fid).get_item(msg_ids, anything){msgs}
|
299
|
+
mock(c).save_messages(msgs)
|
300
|
+
mock(c).delete_messages(fid, msgs)
|
301
|
+
|
302
|
+
mock(fid).get_item(more_msg_ids, anything){more_msgs}
|
303
|
+
mock(c).save_messages(more_msgs)
|
304
|
+
mock(c).delete_messages(fid, more_msgs)
|
305
|
+
|
306
|
+
c.action
|
307
|
+
end
|
308
|
+
|
309
|
+
it "should terminate the fetch loop if no messages are returned" do
|
310
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
311
|
+
|
312
|
+
fid = c.distinguished_folder_ids.first
|
313
|
+
|
314
|
+
state_time = (DateTime.now - 1).to_s
|
315
|
+
state = {fid.key => state_time}
|
316
|
+
stub(c).state{state}
|
317
|
+
|
318
|
+
msg_ids = Object.new
|
319
|
+
stub(msg_ids).length{0}
|
320
|
+
msgs = Object.new
|
321
|
+
|
322
|
+
mock(fid).find_item.with_any_args do |opts|
|
323
|
+
opts[:indexed_page_item_view][:offset].should == 0
|
324
|
+
msg_ids
|
325
|
+
end
|
326
|
+
|
327
|
+
c.action
|
328
|
+
end
|
329
|
+
|
330
|
+
|
331
|
+
end
|
332
|
+
|
333
|
+
describe "mailbox_to_hash" do
|
334
|
+
it "should keep only :name and :email_address keys of a Rews address hash" do
|
335
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
336
|
+
mb = {:name=>"foo bar", :email_address=>"foo@bar.com", :blah=>"blah"}
|
337
|
+
c.mailbox_to_hash(mb).should == {:name=>"foo bar", :email_address=>"foo@bar.com"}
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
describe "mailbox_recipients_to_hashes" do
|
342
|
+
it "should convert a nil recipient to an empty list" do
|
343
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
344
|
+
mbr = {:mailbox=>nil}
|
345
|
+
c.mailbox_recipients_to_hashes(mbr).should == []
|
346
|
+
end
|
347
|
+
|
348
|
+
it "should convert a single recipient to a list of one hash" do
|
349
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
350
|
+
mbr = {:mailbox=>{:name=>"foo bar", :email_address=>"foo@bar.com", :blah=>"blah"}}
|
351
|
+
c.mailbox_recipients_to_hashes(mbr).should == [{:name=>"foo bar", :email_address=>"foo@bar.com"}]
|
352
|
+
end
|
353
|
+
|
354
|
+
it "should convert multiple recipients to a list of hashes" do
|
355
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
356
|
+
mbr = {:mailbox=>[{:name=>"foo bar", :email_address=>"foo@bar.com", :blah=>"blah"},
|
357
|
+
{:name=>"baz mcbaz", :email_address=>"baz.mcbaz@baz.com"}]}
|
358
|
+
c.mailbox_recipients_to_hashes(mbr).should == [{:name=>"foo bar", :email_address=>"foo@bar.com"},
|
359
|
+
{:name=>"baz mcbaz", :email_address=>"baz.mcbaz@baz.com"}]
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
describe "message_to_hash" do
|
364
|
+
it "should convert a message Rews::Item::Item to a hash" do
|
365
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
366
|
+
sent_at = DateTime.now-1
|
367
|
+
m = Rews::Item::Item.new(c,
|
368
|
+
:message,
|
369
|
+
:item_id=>{:id=>"abc", :change_key=>"def"},
|
370
|
+
:internet_message_id=>"<abc123>",
|
371
|
+
:date_time_sent=>sent_at,
|
372
|
+
:date_time_received=>DateTime.now,
|
373
|
+
:in_reply_to=>"<foo>",
|
374
|
+
:references=>["<foo>", "<bar>"],
|
375
|
+
:from=>{:mailbox=>{:name=>"foo mcfoo", :email_address=>"foo.mcfoo@foo.com"}},
|
376
|
+
:sender=>{:mailbox=>{:name=>"mrs mcmrs", :email_address=>"mrs.mcmrs@foo.com"}},
|
377
|
+
:to_recipients=>{:mailbox=>[{:name=>"bar mcbar", :email_address=>"bar.mcbar@bar.com"}, {:name=>"baz mcbaz", :email_address=>"baz.mcbaz@baz.com"}]},
|
378
|
+
:cc_recipients=>{:mailbox=>{:name=>"woo wuwoo", :email_address=>"woo.wuwoo@woo.com"}},
|
379
|
+
:bcc_recipients=>{:mailbox=>{:name=>"fee mcfee", :email_address=>"fee.mcfee@fee.com"}})
|
380
|
+
h=c.message_to_hash(m)
|
381
|
+
h.should == {
|
382
|
+
:message_id=>"abc123",
|
383
|
+
:sent_at=>sent_at.to_s,
|
384
|
+
:in_reply_to=>"foo",
|
385
|
+
:references=>["foo", "bar"],
|
386
|
+
:from=>{:name=>"foo mcfoo", :email_address=>"foo.mcfoo@foo.com"},
|
387
|
+
:sender=>{:name=>"mrs mcmrs", :email_address=>"mrs.mcmrs@foo.com"},
|
388
|
+
:to=>[{:name=>"bar mcbar", :email_address=>"bar.mcbar@bar.com"},
|
389
|
+
{:name=>"baz mcbaz", :email_address=>"baz.mcbaz@baz.com"}],
|
390
|
+
:cc=>[{:name=>"woo wuwoo", :email_address=>"woo.wuwoo@woo.com"}],
|
391
|
+
:bcc=>[{:name=>"fee mcfee", :email_address=>"fee.mcfee@fee.com"}]
|
392
|
+
}
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
describe "save_messages" do
|
397
|
+
def check_saved_msg(c, msg, json_msg)
|
398
|
+
h = JSON.parse(json_msg)
|
399
|
+
h["type"].should == "email"
|
400
|
+
h["connector"].should == c.name
|
401
|
+
h["source"].should == c.url
|
402
|
+
h["source_id"].should == msg[:item_id][:id]
|
403
|
+
|
404
|
+
end
|
405
|
+
|
406
|
+
it "should save a file for each message result" do
|
407
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
408
|
+
|
409
|
+
|
410
|
+
msg1 = {
|
411
|
+
:item_id=>{:id=>"abc", :change_key=>"def"},
|
412
|
+
"item:DateTimeSent"=>DateTime.now-1,
|
413
|
+
"item:DateTimeReceived"=>DateTime.now,
|
414
|
+
"item:InReplyTo"=>"foo",
|
415
|
+
"message:InternetMessageId"=>"foobar",
|
416
|
+
"message:References"=>"barbar",
|
417
|
+
"messsage:From"=>"foo@bar.com",
|
418
|
+
"message:Sender"=>"foo@bar.com",
|
419
|
+
"message:ToRecipients"=>"baz@bar.com",
|
420
|
+
"message:CcRecipients"=>"abc@def.com",
|
421
|
+
"message:BccRecipients"=>"boss@foo.com"
|
422
|
+
}
|
423
|
+
msg2 = {
|
424
|
+
:item_id=>{:id=>"ghi", :change_key=>"jkl"}}
|
425
|
+
|
426
|
+
msgs = [msg1, msg2]
|
427
|
+
|
428
|
+
mock(c.filestore).write(:complete, "#{MD5.hexdigest('abc')}.json", anything) do |*args|
|
429
|
+
check_saved_msg(c, msg1, args.last)
|
430
|
+
end
|
431
|
+
mock(c.filestore).write(:complete, "#{MD5.hexdigest('ghi')}.json", anything) do |*args|
|
432
|
+
check_saved_msg(c, msg2, args.last)
|
433
|
+
end
|
434
|
+
|
435
|
+
c.save_messages(msgs)
|
436
|
+
end
|
437
|
+
|
438
|
+
it "should log and continue if the extracted message is nil" do
|
439
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
440
|
+
stub(c).is_journal{true}
|
441
|
+
|
442
|
+
msg = Object.new
|
443
|
+
msgs=[msg]
|
444
|
+
stub(c).extract_journalled_message(msg){nil}
|
445
|
+
|
446
|
+
stub(c.log).warn(/no data extracted/)
|
447
|
+
dont_allow(c.filestore).write
|
448
|
+
|
449
|
+
c.save_messages(msgs)
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
describe "extract_journalled_message" do
|
454
|
+
it "should catch any exception extracting the message, log a warning and return nil" do
|
455
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
456
|
+
|
457
|
+
msg = Object.new
|
458
|
+
mime_content = Object.new
|
459
|
+
stub(msg).[](:mime_content){mime_content}
|
460
|
+
mime_msg = Object.new
|
461
|
+
stub(Base64).decode64(mime_content){mime_msg}
|
462
|
+
e = begin ; raise "bang" ; rescue Exception=>e ; e ; end
|
463
|
+
stub(Rfc822Util).extract_journalled_mail(mime_msg){raise e}
|
464
|
+
|
465
|
+
stub(c.log).warn(/problem/)
|
466
|
+
stub(c.log).warn(e)
|
467
|
+
|
468
|
+
lambda{
|
469
|
+
c.extract_journalled_message(msg)
|
470
|
+
}.should_not raise_error
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
describe "delete_messages" do
|
475
|
+
it "should HardDelete all messages from a result" do
|
476
|
+
c=Sonar::Connector::EwsPullConnector.new(one_folder_config, @base_config)
|
477
|
+
fid = c.distinguished_folder_ids.first
|
478
|
+
|
479
|
+
msgs = Object.new
|
480
|
+
mock(msgs).length{1}
|
481
|
+
|
482
|
+
mock(fid).delete_item(msgs, :delete_type=>:HardDelete)
|
483
|
+
|
484
|
+
c.delete_messages(fid, msgs)
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'rubygems'
|
4
|
+
require 'spec'
|
5
|
+
require 'spec/autorun'
|
6
|
+
require 'rr'
|
7
|
+
require 'ews_pull_connector/ews_pull_connector'
|
8
|
+
require 'sonar_connector/rspec/spec_helper'
|
9
|
+
|
10
|
+
Spec::Runner.configure do |config|
|
11
|
+
config.mock_with RR::Adapters::Rspec
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sonar_ews_pull_connector
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- mccraigmccraig
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-03-25 00:00:00 +00:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: sonar_connector
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 7
|
33
|
+
- 2
|
34
|
+
version: 0.7.2
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: savon
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 51
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 8
|
49
|
+
- 6
|
50
|
+
version: 0.8.6
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: ntlm-http
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 25
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
- 1
|
65
|
+
- 1
|
66
|
+
version: 0.1.1
|
67
|
+
type: :runtime
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: httpclient
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 217
|
78
|
+
segments:
|
79
|
+
- 2
|
80
|
+
- 1
|
81
|
+
- 6
|
82
|
+
- 1
|
83
|
+
- 1
|
84
|
+
version: 2.1.6.1.1
|
85
|
+
type: :runtime
|
86
|
+
version_requirements: *id004
|
87
|
+
- !ruby/object:Gem::Dependency
|
88
|
+
name: fetch_in
|
89
|
+
prerelease: false
|
90
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
hash: 23
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
- 2
|
99
|
+
- 0
|
100
|
+
version: 0.2.0
|
101
|
+
type: :runtime
|
102
|
+
version_requirements: *id005
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: rfc822_util
|
105
|
+
prerelease: false
|
106
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
hash: 25
|
112
|
+
segments:
|
113
|
+
- 0
|
114
|
+
- 1
|
115
|
+
- 1
|
116
|
+
version: 0.1.1
|
117
|
+
type: :runtime
|
118
|
+
version_requirements: *id006
|
119
|
+
- !ruby/object:Gem::Dependency
|
120
|
+
name: rspec
|
121
|
+
prerelease: false
|
122
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
hash: 13
|
128
|
+
segments:
|
129
|
+
- 1
|
130
|
+
- 2
|
131
|
+
- 9
|
132
|
+
version: 1.2.9
|
133
|
+
type: :development
|
134
|
+
version_requirements: *id007
|
135
|
+
- !ruby/object:Gem::Dependency
|
136
|
+
name: rr
|
137
|
+
prerelease: false
|
138
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
139
|
+
none: false
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
hash: 61
|
144
|
+
segments:
|
145
|
+
- 0
|
146
|
+
- 10
|
147
|
+
- 5
|
148
|
+
version: 0.10.5
|
149
|
+
type: :development
|
150
|
+
version_requirements: *id008
|
151
|
+
description: A sonar-connector for extracting emails from Exchange 2007/2010 through Exchange Web Services
|
152
|
+
email: craig@trampolinesystems.com
|
153
|
+
executables: []
|
154
|
+
|
155
|
+
extensions: []
|
156
|
+
|
157
|
+
extra_rdoc_files:
|
158
|
+
- LICENSE
|
159
|
+
- README.rdoc
|
160
|
+
files:
|
161
|
+
- .document
|
162
|
+
- LICENSE
|
163
|
+
- README.rdoc
|
164
|
+
- Rakefile
|
165
|
+
- VERSION
|
166
|
+
- lib/ews_pull_connector/ews_pull_connector.rb
|
167
|
+
- lib/sonar_ews_pull_connector.rb
|
168
|
+
- spec/ews_pull_connector/ews_pull_connector_spec.rb
|
169
|
+
- spec/spec.opts
|
170
|
+
- spec/spec_helper.rb
|
171
|
+
has_rdoc: true
|
172
|
+
homepage: http://github.com/trampoline/sonar-ews-pull-connector
|
173
|
+
licenses: []
|
174
|
+
|
175
|
+
post_install_message:
|
176
|
+
rdoc_options: []
|
177
|
+
|
178
|
+
require_paths:
|
179
|
+
- lib
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
181
|
+
none: false
|
182
|
+
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
hash: 3
|
186
|
+
segments:
|
187
|
+
- 0
|
188
|
+
version: "0"
|
189
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
|
+
none: false
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
hash: 3
|
195
|
+
segments:
|
196
|
+
- 0
|
197
|
+
version: "0"
|
198
|
+
requirements: []
|
199
|
+
|
200
|
+
rubyforge_project:
|
201
|
+
rubygems_version: 1.6.2
|
202
|
+
signing_key:
|
203
|
+
specification_version: 3
|
204
|
+
summary: Exchange Web Services connector for Exchange 2007/2010
|
205
|
+
test_files:
|
206
|
+
- spec/ews_pull_connector/ews_pull_connector_spec.rb
|
207
|
+
- spec/spec_helper.rb
|