rexchange 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +11 -0
- data/RAKEFILE +8 -1
- data/README +2 -2
- data/lib/rexchange.rb +2 -2
- data/lib/rexchange/appointment.rb +38 -0
- data/lib/rexchange/contact.rb +21 -38
- data/lib/rexchange/credentials.rb +3 -3
- data/lib/rexchange/folder.rb +47 -67
- data/lib/rexchange/generic_item.rb +87 -28
- data/lib/rexchange/message.rb +15 -28
- data/lib/rexchange/session.rb +3 -3
- data/test/functional.rb +4 -9
- metadata +43 -36
data/CHANGELOG
CHANGED
@@ -1,8 +1,19 @@
|
|
1
|
+
-- 0.3.0:
|
2
|
+
* Added support for Appointments
|
3
|
+
* Options hash for Session creation is gone. The new form is: "RExchange::Session.new(url, username, password)"
|
4
|
+
* Removed GenericItem#[], GenericItem#method_missing (attributes are explicit now)
|
5
|
+
* Removed Folder#get_messages, #get_contacts and #messages_in. Folders are now Enumerables using Enumerable#entries, #each, etc
|
6
|
+
to retrieve entries according to the folder's content-class. So a 'mailfolder' will retrieve Message instances,
|
7
|
+
a 'calendarfolder' will retrieve Appointment instances, and so on.
|
8
|
+
|
1
9
|
-- 0.2.0:
|
2
10
|
* Fixed a bug in Message#move_to when passing folders
|
3
11
|
* Added support for Contact browsing
|
4
12
|
* Added GenericItem to abstract Contacts and Messages
|
5
13
|
* Renamed Folder#messages to Folder#get_messages to avoid naming collisions
|
14
|
+
* Modified attributes in GenericItem that end with "date" to be Time::parse'd
|
15
|
+
* Added "r_exchange.rb" for Rails auto-require compatibility
|
16
|
+
* Now calling String#normalize on attribute names instead of just String#tr('-', '_')
|
6
17
|
|
7
18
|
-- 0.1.4:
|
8
19
|
* Moved Folder::normalize_folder_name to String#normalize, and added support for MixedCase sources.
|
data/RAKEFILE
CHANGED
@@ -2,12 +2,13 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'rake'
|
5
|
+
require 'rake/testtask'
|
5
6
|
require 'rake/rdoctask'
|
6
7
|
require 'rake/gempackagetask'
|
7
8
|
require 'rake/contrib/rubyforgepublisher'
|
8
9
|
require 'pscp'
|
9
10
|
|
10
|
-
PACKAGE_VERSION = '0.
|
11
|
+
PACKAGE_VERSION = '0.3.0'
|
11
12
|
|
12
13
|
PACKAGE_FILES = FileList[
|
13
14
|
'README',
|
@@ -64,4 +65,10 @@ end
|
|
64
65
|
desc "Publish RDOC to RubyForge"
|
65
66
|
task :rubyforge => [:rdoc, :gem] do
|
66
67
|
Rake::SshDirPublisher.new(ENV['RUBYFORGE_USER'], ENV['RUBYFORGE_PROJECT'], 'doc').upload
|
68
|
+
end
|
69
|
+
|
70
|
+
Rake::TestTask.new do |t|
|
71
|
+
t.libs << "test"
|
72
|
+
t.test_files = FileList['test/*.rb']
|
73
|
+
t.verbose = true
|
67
74
|
end
|
data/README
CHANGED
@@ -4,7 +4,7 @@ RExchange is a pure ruby wrapper for the Microsoft Exchange Server WebDAV API
|
|
4
4
|
|
5
5
|
== Things you should know
|
6
6
|
|
7
|
-
* Requires Ruby 1.8.4 (for the extended WebDAV support in the net/http library)
|
7
|
+
* Requires Ruby 1.8.4 (or later, for the extended WebDAV support in the net/http library)
|
8
8
|
* RExchange is cross-platform compatible, being written in pure Ruby
|
9
9
|
* Kiwi fruits are packed with vitamins
|
10
10
|
|
@@ -36,7 +36,7 @@ RExchange is a pure ruby wrapper for the Microsoft Exchange Server WebDAV API
|
|
36
36
|
|
37
37
|
# The RExchange::Message#move_to method moves the message to another folder, in this
|
38
38
|
# case, an "archive" folder off of the inbox.
|
39
|
-
message.move_to '/inbox/archive'
|
39
|
+
message.move_to mailbox.inbox.archive # or you could pass the string: '/inbox/archive'
|
40
40
|
end
|
41
41
|
|
42
42
|
# You can also call the RExchange::Folder#messages method if you find calling "each"
|
data/lib/rexchange.rb
CHANGED
@@ -19,8 +19,8 @@ module RExchange
|
|
19
19
|
DEBUG_STREAM = $log
|
20
20
|
|
21
21
|
# A shortcut to RExchange::Session#new's block syntax
|
22
|
-
def self.open(uri,
|
23
|
-
session = RExchange::Session.new(uri,
|
22
|
+
def self.open(uri, username = nil, password = nil)
|
23
|
+
session = RExchange::Session.new(uri, username, password)
|
24
24
|
|
25
25
|
yield session if block_given?
|
26
26
|
return session
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rexchange/generic_item'
|
2
|
+
|
3
|
+
module RExchange
|
4
|
+
class Appointment < GenericItem
|
5
|
+
|
6
|
+
set_folder_type 'calendar'
|
7
|
+
|
8
|
+
attribute_mappings :all_day_event => 'urn:schemas:calendar:alldayevent',
|
9
|
+
:busy_status => 'urn:schemas:calendar:busystatus',
|
10
|
+
:contact => 'urn:schemas:calendar:contact',
|
11
|
+
:contact_url => 'urn:schemas:calendar:contacturl',
|
12
|
+
:created_on => 'urn:schemas:calendar:created',
|
13
|
+
:description_url => 'urn:schemas:calendar:descriptionurl',
|
14
|
+
:end_at => 'urn:schemas:calendar:dtend',
|
15
|
+
:created_at => 'urn:schemas:calendar:dtstamp',
|
16
|
+
:start_at => 'urn:schemas:calendar:dtstart',
|
17
|
+
:duration => 'urn:schemas:calendar:duration',
|
18
|
+
:expires_on => 'urn:schemas:calendar:exdate',
|
19
|
+
:expiry_rule => 'urn:schemas:calendar:exrule',
|
20
|
+
:has_attachment? => 'urn:schemas:httpmail:hasattachment',
|
21
|
+
:html => 'urn:schemas:httpmail:htmldescription',
|
22
|
+
:modified_on => 'urn:schemas:calendar:lastmodified',
|
23
|
+
:location => 'urn:schemas:calendar:location',
|
24
|
+
:location_url => 'urn:schemas:calendar:locationurl',
|
25
|
+
:meeting_status => 'urn:schemas:calendar:meetingstatus',
|
26
|
+
:normalized_subject => 'urn:schemas:httpmail:normalizedsubject',
|
27
|
+
:priority => 'urn:schemas:httpmail:priority',
|
28
|
+
:recurres_on => 'urn:schemas:calendar:rdate',
|
29
|
+
:reminder_offset => 'urn:schemas:calendar:reminderoffset',
|
30
|
+
:reply_time => 'urn:schemas:calendar:replytime',
|
31
|
+
:sequence => 'urn:schemas:calendar:sequence',
|
32
|
+
:subject => 'urn:schemas:httpmail:subject',
|
33
|
+
:body => 'urn:schemas:httpmail:textdescription',
|
34
|
+
:timezone => 'urn:schemas:calendar:timezone',
|
35
|
+
:uid => 'urn:schemas:calendar:uid'
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/rexchange/contact.rb
CHANGED
@@ -3,43 +3,26 @@ require 'rexchange/generic_item'
|
|
3
3
|
module RExchange
|
4
4
|
class Contact < GenericItem
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
<D:sql>
|
27
|
-
SELECT "urn:schemas:contacts:givenName", "urn:schemas:contacts:middlename",
|
28
|
-
"urn:schemas:contacts:sn", "urn:schemas:contacts:title",
|
29
|
-
"urn:schemas:contacts:mailingstreet", "urn:schemas:contacts:mailingcity",
|
30
|
-
"urn:schemas:contacts:st", "urn:schemas:contacts:mailingpostalcode",
|
31
|
-
"urn:schemas:contacts:co", "urn:schemas:contacts:homePhone",
|
32
|
-
"urn:schemas:contacts:telephoneNumber", "urn:schemas:contacts:facsimiletelephonenumber",
|
33
|
-
"urn:schemas:contacts:mobile", "urn:schemas:contacts:email1",
|
34
|
-
"urn:schemas:contacts:businesshomepage", "urn:schemas:contacts:o", "DAV:creationdate"
|
35
|
-
FROM SCOPE('shallow traversal of "#{path}"')
|
36
|
-
WHERE "DAV:ishidden" = false
|
37
|
-
AND "DAV:isfolder" = false
|
38
|
-
AND "DAV:contentclass" = 'urn:content-classes:person'
|
39
|
-
</D:sql>
|
40
|
-
</D:searchrequest>
|
41
|
-
QBODY
|
42
|
-
end
|
6
|
+
set_folder_type 'contact'
|
7
|
+
set_content_class 'person'
|
8
|
+
|
9
|
+
attribute_mappings :first_name => 'urn:schemas:contacts:givenName',
|
10
|
+
:middle_name => 'urn:schemas:contacts:middlename',
|
11
|
+
:last_name => 'urn:schemas:contacts:sn',
|
12
|
+
:title => 'urn:schemas:contacts:title',
|
13
|
+
:created_at => 'DAV:creationdate',
|
14
|
+
:address => 'urn:schemas:contacts:mailingstreet',
|
15
|
+
:city => 'urn:schemas:contacts:mailingcity',
|
16
|
+
:state => 'urn:schemas:contacts:st',
|
17
|
+
:zip_code => 'urn:schemas:contacts:mailingpostalcode',
|
18
|
+
:country => 'urn:schemas:contacts:co',
|
19
|
+
:phone => 'urn:schemas:contacts:homePhone',
|
20
|
+
:business_phone => 'urn:schemas:contacts:telephoneNumber',
|
21
|
+
:fax => 'urn:schemas:contacts:facsimiletelephonenumber',
|
22
|
+
:mobile => 'urn:schemas:contacts:mobile',
|
23
|
+
:email => 'urn:schemas:contacts:email1',
|
24
|
+
:website => 'urn:schemas:contacts:businesshomepage',
|
25
|
+
:company => 'urn:schemas:contacts:o'
|
43
26
|
|
44
|
-
end
|
27
|
+
end
|
45
28
|
end
|
@@ -8,11 +8,11 @@ module RExchange
|
|
8
8
|
attr_reader :user, :password, :uri
|
9
9
|
|
10
10
|
# You must pass a uri, and an options hash containing :user and :password
|
11
|
-
def initialize(uri,
|
11
|
+
def initialize(uri, username = nil, password = nil)
|
12
12
|
@uri = URI.parse(uri)
|
13
13
|
@use_ssl = (@uri.scheme.downcase == 'https')
|
14
|
-
@user = @uri.userinfo ? @user.userinfo.split(':')[0] :
|
15
|
-
@password = @uri.userinfo ? @user.userinfo.split(':')[1] :
|
14
|
+
@user = @uri.userinfo ? @user.userinfo.split(':')[0] : username
|
15
|
+
@password = @uri.userinfo ? @user.userinfo.split(':')[1] : password
|
16
16
|
@port = @uri.port || @uri.default_port
|
17
17
|
|
18
18
|
if block_given?
|
data/lib/rexchange/folder.rb
CHANGED
@@ -3,62 +3,43 @@ require 'net/https'
|
|
3
3
|
require 'rexchange/dav_search_request'
|
4
4
|
require 'rexchange/message'
|
5
5
|
require 'rexchange/contact'
|
6
|
+
require 'rexchange/appointment'
|
6
7
|
|
7
8
|
module RExchange
|
8
9
|
|
10
|
+
class FolderNotFoundError < StandardError
|
11
|
+
end
|
12
|
+
|
9
13
|
class Folder
|
10
14
|
include REXML
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
attr_reader :credentails
|
16
|
+
attr_reader :credentails, :name
|
15
17
|
|
16
|
-
def initialize(credentials, parent,
|
17
|
-
@credentials, @parent, @
|
18
|
+
def initialize(credentials, parent, name, content_type)
|
19
|
+
@credentials, @parent, @name = credentials, parent, name
|
20
|
+
@content_type = CONTENT_TYPES[content_type]
|
18
21
|
end
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
# Used to access subfolders. If the subfolder does not
|
23
|
-
# exist, then old_method_missing is called.
|
23
|
+
# Used to access subfolders.
|
24
24
|
def method_missing(sym, *args)
|
25
25
|
if folders.has_key?(sym.to_s)
|
26
|
-
|
26
|
+
folders[sym.to_s]
|
27
27
|
else
|
28
|
-
|
28
|
+
raise FolderNotFoundError.new("#{sym} is not a subfolder of #{name}")
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
|
32
|
+
include Enumerable
|
33
|
+
|
34
|
+
# Iterate through each entry in this folder
|
33
35
|
def each
|
34
|
-
|
35
|
-
get_contacts
|
36
|
-
else
|
37
|
-
get_messages
|
38
|
-
end.each do |item|
|
36
|
+
@content_type::find(@credentials, to_s).each do |item|
|
39
37
|
yield item
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Retrieve an Array of messages from a specific folder
|
44
|
-
# === Example
|
45
|
-
# RExchange::open(uri, :user => 'bob', :password => 'random') do |mailbox|
|
46
|
-
# mailbox.messages_in('inbox/archive').each do |message|
|
47
|
-
# p message.from
|
48
|
-
# end
|
49
|
-
# end
|
50
|
-
def messages_in(folder)
|
51
|
-
folder.split('/').inject(@credentials.uri.path) do |final_path, current_path|
|
52
|
-
Folder.new(@credentials, final_path, current_path)
|
53
|
-
end.get_messages
|
54
|
-
end
|
55
|
-
|
56
|
-
def get_messages
|
57
|
-
RExchange::Message::find(@credentials, to_s)
|
38
|
+
end
|
58
39
|
end
|
59
40
|
|
60
|
-
def
|
61
|
-
|
41
|
+
def search(conditions = {})
|
42
|
+
@content_type::find(@credentials, to_s, conditions)
|
62
43
|
end
|
63
44
|
|
64
45
|
# Join the strings passed in with '/'s between them
|
@@ -68,40 +49,39 @@ module RExchange
|
|
68
49
|
|
69
50
|
# Return an Array of subfolders for this folder
|
70
51
|
def folders
|
71
|
-
@folders ||=
|
52
|
+
@folders ||= begin
|
53
|
+
request_body = <<-eos
|
54
|
+
<?xml version="1.0"?>
|
55
|
+
<D:searchrequest xmlns:D = "DAV:">
|
56
|
+
<D:sql>
|
57
|
+
SELECT "DAV:displayname", "DAV:contentclass"
|
58
|
+
FROM SCOPE('shallow traversal of "#{to_s}"')
|
59
|
+
WHERE "DAV:ishidden" = false
|
60
|
+
AND "DAV:isfolder" = true
|
61
|
+
</D:sql>
|
62
|
+
</D:searchrequest>
|
63
|
+
eos
|
64
|
+
|
65
|
+
response = DavSearchRequest.execute(@credentials, :body => request_body)
|
66
|
+
|
67
|
+
folders = {}
|
68
|
+
|
69
|
+
# iterate through folders query and add a new Folder
|
70
|
+
# object for each, under a normalized name.
|
71
|
+
xpath_query = "//a:propstat[a:status/text() = 'HTTP/1.1 200 OK']/a:prop"
|
72
|
+
Document.new(response.body).elements.each(xpath_query) do |m|
|
73
|
+
displayname = m.elements['a:displayname'].text
|
74
|
+
contentclass = m.elements['a:contentclass'].text
|
75
|
+
folders[displayname.normalize] = Folder.new(@credentials, self, displayname, contentclass.split(':').last.sub(/folder$/, ''))
|
76
|
+
end
|
77
|
+
|
78
|
+
folders
|
79
|
+
end
|
72
80
|
end
|
73
81
|
|
74
82
|
# Return the absolute path to this folder (but not the full URI)
|
75
83
|
def to_s
|
76
|
-
Folder.join(@parent, @
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
def get_folders
|
82
|
-
request_body = <<DA_QUERY
|
83
|
-
<?xml version="1.0"?>
|
84
|
-
<D:searchrequest xmlns:D = "DAV:">
|
85
|
-
<D:sql>
|
86
|
-
SELECT "DAV:displayname"
|
87
|
-
FROM SCOPE('shallow traversal of "#{to_s}"')
|
88
|
-
WHERE "DAV:ishidden" = false
|
89
|
-
AND "DAV:isfolder" = true
|
90
|
-
</D:sql>
|
91
|
-
</D:searchrequest>
|
92
|
-
DA_QUERY
|
93
|
-
|
94
|
-
response = DavSearchRequest.execute(@credentials, :body => request_body)
|
95
|
-
|
96
|
-
folders = {}
|
97
|
-
|
98
|
-
# iterate through folders query and add each normalized name to the folders array.
|
99
|
-
xpath_query = "//a:propstat[a:status/text() = 'HTTP/1.1 200 OK']/a:prop"
|
100
|
-
Document.new(response.body).elements.each(xpath_query) do |m|
|
101
|
-
folders[m.elements['a:displayname'].text.normalize] = m.elements['a:displayname'].text
|
102
|
-
end
|
103
|
-
|
104
|
-
return folders
|
84
|
+
Folder.join(@parent, @name)
|
105
85
|
end
|
106
86
|
end
|
107
87
|
end
|
@@ -4,61 +4,120 @@ require 'rexchange/dav_move_request'
|
|
4
4
|
require 'time'
|
5
5
|
|
6
6
|
module RExchange
|
7
|
+
|
8
|
+
class Folder
|
9
|
+
CONTENT_TYPES = {}
|
10
|
+
end
|
11
|
+
|
7
12
|
class GenericItem
|
8
13
|
include REXML
|
9
14
|
include Enumerable
|
10
|
-
|
11
|
-
attr_accessor :attributes
|
12
|
-
|
13
|
-
# Used to access the attributes of the item as a hash.
|
14
|
-
def [](key)
|
15
|
-
return @attributes[key]
|
16
|
-
end
|
17
|
-
|
18
|
-
# Used to access the attributes of the item.
|
19
|
-
def method_missing(sym, *args)
|
20
|
-
return @attributes[sym.to_s] if @attributes.include?(sym.to_s)
|
21
|
-
end
|
22
|
-
|
15
|
+
|
16
|
+
attr_accessor :attributes
|
17
|
+
|
23
18
|
def initialize(session, dav_property_node)
|
24
19
|
@attributes = {}
|
25
20
|
@session = session
|
26
|
-
|
21
|
+
|
27
22
|
dav_property_node.elements.each do |element|
|
28
|
-
|
29
|
-
|
23
|
+
namespaced_name = element.namespace + element.name
|
24
|
+
|
25
|
+
if element.name =~ /date$/i || self.class::ATTRIBUTE_MAPPINGS.find { |k,v| v == namespaced_name && k.to_s =~ /\_(at|on)$/ }
|
26
|
+
@attributes[namespaced_name] = Time::parse(element.text)
|
30
27
|
else
|
31
|
-
@attributes[
|
28
|
+
@attributes[namespaced_name] = element.text
|
32
29
|
end
|
33
30
|
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set the default CONTENT_CLASS to the class name, and define a
|
34
|
+
# dynamic query method for the derived class.
|
35
|
+
def self.inherited(base)
|
36
|
+
base.const_set('CONTENT_CLASS', base.to_s.split('::').last.downcase)
|
34
37
|
|
35
|
-
|
38
|
+
def base.query(path)
|
39
|
+
<<-QBODY
|
40
|
+
SELECT
|
41
|
+
#{self::ATTRIBUTE_MAPPINGS.values.map { |f| '"' + f + '"' }.join(',')}
|
42
|
+
FROM SCOPE('shallow traversal of "#{path}"')
|
43
|
+
WHERE "DAV:ishidden" = false
|
44
|
+
AND "DAV:isfolder" = false
|
45
|
+
AND "DAV:contentclass" = 'urn:content-classes:#{self::CONTENT_CLASS}'
|
46
|
+
QBODY
|
47
|
+
end
|
36
48
|
end
|
37
49
|
|
50
|
+
# This handy method is meant to be called from any inheriting
|
51
|
+
# classes. It is used to bind types of folders to particular
|
52
|
+
# Entity classes so that the folder knows what type it's
|
53
|
+
# enumerating. So for a "calendarfolder" you'd call:
|
54
|
+
# set_folder_type 'calendarfolder' # or just 'calendar'
|
55
|
+
def self.set_folder_type(dav_name)
|
56
|
+
Folder::CONTENT_TYPES[dav_name.sub(/folder$/, '')] = self
|
57
|
+
end
|
58
|
+
|
59
|
+
# --Normally Not Used--
|
60
|
+
# By default the CONTENT_CLASS is determined by the name
|
61
|
+
# of your class. So for the Appointment class the
|
62
|
+
# CONTENT_CLASS would be 'appointment'.
|
63
|
+
# If for some reason this convention doesn't suit you,
|
64
|
+
# you can use this method to set the appropriate value
|
65
|
+
# (which is used in queries).
|
66
|
+
# For example, the DAV:content-class for contacts is:
|
67
|
+
# 'urn:content-classes:person'
|
68
|
+
# Person doesn't strike me as the best name for our class though.
|
69
|
+
# Most people would refer to an entry in a Contacts folder as
|
70
|
+
# a Contact. So that's what we call our class, and we use this method
|
71
|
+
# to make sure everything still works as it should.
|
72
|
+
def self.set_content_class(dav_name)
|
73
|
+
const_set('CONTENT_CLASS', dav_name)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Defines what attributes are used in queries, and
|
77
|
+
# what methods they map to in instances. You should
|
78
|
+
# pass a Hash of method_name and namespaced-attribute-name pairs.
|
38
79
|
def self.attribute_mappings(mappings)
|
80
|
+
|
81
|
+
mappings.merge! :uid => 'DAV:uid',
|
82
|
+
:modified_at => 'DAV:getlastmodified',
|
83
|
+
:href => 'DAV:href'
|
84
|
+
|
85
|
+
const_set('ATTRIBUTE_MAPPINGS', mappings)
|
86
|
+
|
39
87
|
mappings.each_pair do |k,v|
|
40
|
-
|
88
|
+
|
89
|
+
define_method(k) do
|
41
90
|
@attributes[v]
|
42
91
|
end
|
92
|
+
|
93
|
+
define_method("#{k.to_s.sub(/\?$/, '')}=") do |value|
|
94
|
+
@attributes[v] = value
|
95
|
+
end
|
96
|
+
|
43
97
|
end
|
44
98
|
end
|
45
99
|
|
46
|
-
def self.query(path)
|
47
|
-
raise 'YOU MUST DEFINE THIS'
|
48
|
-
end
|
49
|
-
|
50
100
|
# Retrieve an Array of items (such as Contact, Message, etc)
|
51
|
-
def self.find(credentials, path)
|
52
|
-
|
101
|
+
def self.find(credentials, path, conditions = nil)
|
102
|
+
qbody = <<-QBODY
|
103
|
+
<?xml version="1.0"?>
|
104
|
+
<D:searchrequest xmlns:D = "DAV:">
|
105
|
+
<D:sql>
|
106
|
+
#{conditions.nil? ? query(path) : search(path, conditions)}
|
107
|
+
</D:sql>
|
108
|
+
</D:searchrequest>
|
109
|
+
QBODY
|
53
110
|
|
111
|
+
response = DavSearchRequest.execute(credentials, :body => qbody)
|
112
|
+
|
54
113
|
items = []
|
55
114
|
xpath_query = "//a:propstat[a:status/text() = 'HTTP/1.1 200 OK']/a:prop"
|
56
|
-
|
115
|
+
|
57
116
|
Document.new(response.body).elements.each(xpath_query) do |m|
|
58
117
|
items << self.new(credentials, m)
|
59
118
|
end
|
60
|
-
|
61
|
-
|
119
|
+
|
120
|
+
items
|
62
121
|
end
|
63
122
|
end
|
64
123
|
end
|
data/lib/rexchange/message.rb
CHANGED
@@ -3,17 +3,17 @@ require 'rexchange/generic_item'
|
|
3
3
|
module RExchange
|
4
4
|
class Message < GenericItem
|
5
5
|
|
6
|
-
|
7
|
-
"To: #{to}, From: #{from}, Subject: #{subject}"
|
8
|
-
end
|
6
|
+
set_folder_type 'mail'
|
9
7
|
|
10
|
-
attribute_mappings
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
attribute_mappings :from => 'urn:schemas:httpmail:from',
|
9
|
+
:to => 'urn:schemas:httpmail:to',
|
10
|
+
:message_id => 'urn:schemas:mailheader:message-id',
|
11
|
+
:subject => 'urn:schemas:httpmail:subject',
|
12
|
+
:recieved_on => 'urn:schemas:httpmail:date',
|
13
|
+
:importance => 'urn:schemas:httpmail:importance',
|
14
|
+
:has_attachments? => 'urn:schemas:httpmail:hasattachment',
|
15
|
+
:body => 'urn:schemas:httpmail:textdescription',
|
16
|
+
:html => 'urn:schemas:httpmail:htmldescription'
|
17
17
|
|
18
18
|
# Move this message to the specified folder.
|
19
19
|
# The folder can be a string such as 'inbox/archive' or a RExchange::Folder.
|
@@ -28,28 +28,15 @@ module RExchange
|
|
28
28
|
else
|
29
29
|
@session.uri.path.ensure_ends_with('/') + folder.to_s.ensure_ends_with('/') + source.split('/').last
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
$log.debug "move_to: source => \"#{source}\", destination => \"#{destination}\""
|
33
33
|
DavMoveRequest.execute(@session, source, destination)
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
<D:searchrequest xmlns:D = "DAV:">
|
40
|
-
<D:sql>
|
41
|
-
SELECT "DAV:href", "urn:schemas:httpmail:from", "urn:schemas:httpmail:to",
|
42
|
-
"urn:schemas:mailheader:message-id", "urn:schemas:httpmail:subject",
|
43
|
-
"urn:schemas:httpmail:date", "urn:schemas:httpmail:importance",
|
44
|
-
"urn:schemas:httpmail:hasattachment", "urn:schemas:httpmail:textdescription",
|
45
|
-
"urn:schemas:httpmail:htmldescription"
|
46
|
-
FROM SCOPE('shallow traversal of "#{path}"')
|
47
|
-
WHERE "DAV:ishidden" = false
|
48
|
-
AND "DAV:isfolder" = false
|
49
|
-
AND "DAV:contentclass" = 'urn:content-classes:message'
|
50
|
-
</D:sql>
|
51
|
-
</D:searchrequest>
|
52
|
-
QBODY
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
"To: #{to}, From: #{from}, Subject: #{subject}"
|
53
39
|
end
|
40
|
+
|
54
41
|
end
|
55
42
|
end
|
data/lib/rexchange/session.rb
CHANGED
@@ -11,14 +11,14 @@ module RExchange
|
|
11
11
|
# uri = 'https://mydomain.com/exchange/demo'
|
12
12
|
# options = { :user => 'test', :password => 'random' }
|
13
13
|
#
|
14
|
-
# RExchange::Session.new(uri,
|
14
|
+
# RExchange::Session.new(uri, 'bob', 'secret') do |mailbox|
|
15
15
|
# mailbox.test.each do |message|
|
16
16
|
# puts message.subject
|
17
17
|
# end
|
18
18
|
# end
|
19
|
-
def initialize(uri,
|
19
|
+
def initialize(uri, username = nil, password = nil)
|
20
20
|
|
21
|
-
@credentials = Credentials.new(uri,
|
21
|
+
@credentials = Credentials.new(uri, username, password)
|
22
22
|
@parent = @credentials.uri.path
|
23
23
|
@folder = ''
|
24
24
|
|
data/test/functional.rb
CHANGED
@@ -4,24 +4,19 @@ require 'rexchange'
|
|
4
4
|
class FunctionalTests < Test::Unit::TestCase
|
5
5
|
|
6
6
|
def setup
|
7
|
+
@mailbox = RExchange::Session.new 'url', 'username', 'password'
|
7
8
|
end
|
8
9
|
|
9
10
|
def teardown
|
11
|
+
@mailbox = nil
|
10
12
|
end
|
11
13
|
|
12
14
|
# Ok, so it's not a real test, but I needed to get started,
|
13
15
|
# and get rid of console scripts.
|
14
16
|
def test_no_exceptions
|
15
|
-
|
16
|
-
uri = "https://#{ENV['rexchange_test_server']}/exchange/#{ENV['rexchange_test_mailbox']}/"
|
17
|
-
options = { :user => ENV['rexchange_test_user'], :password => ENV['rexchange_test_password'] }
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
puts message.body
|
22
|
-
end
|
23
|
-
|
24
|
-
mailbox.folders.each { |folder| puts folder }
|
18
|
+
@mailbox.inbox.search(:from => 'scott').each do |m|
|
19
|
+
puts m.subject
|
25
20
|
end
|
26
21
|
|
27
22
|
end
|
metadata
CHANGED
@@ -1,60 +1,67 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.
|
2
|
+
rubygems_version: 0.9.0
|
3
3
|
specification_version: 1
|
4
4
|
name: rexchange
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date:
|
8
|
-
summary:
|
6
|
+
version: 0.3.0
|
7
|
+
date: 2007-01-05 00:00:00 -06:00
|
8
|
+
summary: A simple wrapper around Microsoft Exchange Server's WebDAV API
|
9
9
|
require_paths:
|
10
|
-
|
10
|
+
- lib
|
11
11
|
email: ssmoot@gmail.com; bauer.mail@gmail.com
|
12
12
|
homepage: http://substantiality.net
|
13
13
|
rubyforge_project: rexchange
|
14
|
-
description:
|
14
|
+
description: Connect, browse, and iterate through folders and messages on an Exchange Server
|
15
15
|
autorequire: rexchange
|
16
16
|
default_executable:
|
17
17
|
bindir: bin
|
18
18
|
has_rdoc: true
|
19
19
|
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
20
|
requirements:
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
version: 0.0.0
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
25
24
|
version:
|
26
25
|
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
27
29
|
authors:
|
28
|
-
|
29
|
-
|
30
|
+
- Sam Smoot
|
31
|
+
- Scott Bauer
|
30
32
|
files:
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
33
|
+
- README
|
34
|
+
- CHANGELOG
|
35
|
+
- RAKEFILE
|
36
|
+
- lib/rexchange.rb
|
37
|
+
- lib/r_exchange.rb
|
38
|
+
- lib/rexchange/appointment.rb
|
39
|
+
- lib/rexchange/contact.rb
|
40
|
+
- lib/rexchange/credentials.rb
|
41
|
+
- lib/rexchange/dav_move_request.rb
|
42
|
+
- lib/rexchange/dav_search_request.rb
|
43
|
+
- lib/rexchange/exchange_request.rb
|
44
|
+
- lib/rexchange/folder.rb
|
45
|
+
- lib/rexchange/generic_item.rb
|
46
|
+
- lib/rexchange/message.rb
|
47
|
+
- lib/rexchange/session.rb
|
48
|
+
- test/functional.rb
|
46
49
|
test_files: []
|
50
|
+
|
47
51
|
rdoc_options:
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
+
- --line-numbers
|
53
|
+
- --inline-source
|
54
|
+
- --main
|
55
|
+
- README
|
52
56
|
extra_rdoc_files:
|
53
|
-
|
54
|
-
|
55
|
-
|
57
|
+
- README
|
58
|
+
- CHANGELOG
|
59
|
+
- RAKEFILE
|
56
60
|
executables: []
|
61
|
+
|
57
62
|
extensions: []
|
63
|
+
|
58
64
|
requirements:
|
59
|
-
|
60
|
-
dependencies: []
|
65
|
+
- none
|
66
|
+
dependencies: []
|
67
|
+
|