sonar_ews_pull_connector 0.2.0
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.
- 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
|