rexchange 0.2.0 → 0.3.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/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
|
+
|