simple_dav 0.0.5 → 0.0.6

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/README ADDED
@@ -0,0 +1,51 @@
1
+ This class acts as a Ruby dav client. First Goal is to sync sogo with RoR apps
2
+
3
+ First release allow to connect to sogo address book
4
+
5
+ INSTALL :
6
+
7
+ > gem install simple_dav
8
+
9
+ USE :
10
+
11
+ Example:
12
+
13
+ include SimpleDav
14
+
15
+ server = "mysogoserver.net"
16
+ ab = "personal"
17
+ user = "user"
18
+ pass = "secret"
19
+
20
+ adb = AddressBook.new(ab, :server => server,:verify => false, :ssl => true, :user => user, :pass => pass, :type => "sogo")
21
+
22
+ cards = Card.where(:email => "od@idfuze.com")
23
+ card = cards.first
24
+ card.to_s
25
+ > PRODID:-//IDFuze//SimpleDav//EN
26
+ > VERSION:3.0
27
+ > CLASS:PUBLIC
28
+ > PROFILE:VCARD
29
+ > N:titi
30
+ > EMAIL:od@idfuze.com
31
+ > UID:38294-1348845607-45923.vcf
32
+ > END:VCARD
33
+ card.update(:n => "Olivier", :fn => "dev")
34
+ > PRODID:-//IDFuze//SimpleDav//EN
35
+ > VERSION:3.0
36
+ > CLASS:PUBLIC
37
+ > PROFILE:VCARD
38
+ > N:Olivier
39
+ > EMAIL:od@idfuze.com
40
+ > UID:38294-1348845607-45923.vcf
41
+ > FN:dev
42
+ > END:VCARD
43
+ card.uid
44
+ > "UID:38294-1348845607-45923.vcf"
45
+ card.delete
46
+
47
+
48
+ Many thanks to SOGo team you make a wonderfull work and i really want to add a little brick with that !
49
+
50
+
51
+ Copyright (c) 2012 IDFUZE.COM / Olivier DIRRENBERGER
data/TODO ADDED
@@ -0,0 +1,7 @@
1
+ Add look up for other calendar than personnal
2
+ Add events support
3
+ Add tasks support
4
+ Add files support
5
+ Support for locking
6
+ Support for access control
7
+ Support for vcard group managment
@@ -0,0 +1,164 @@
1
+ module SimpleDav
2
+ class AddressBook < Base
3
+ attr_reader :group
4
+ def initialize(params)
5
+ abu = "personal"
6
+ @vcard = nil
7
+ super(params)
8
+ @abu = @type == "sogo" ? "Contacts/#{abu}/" : "#{abu}"
9
+ @uri.path += @abu
10
+ @group = nil #store group uid
11
+ Card.adb = self
12
+ end
13
+
14
+ # select address book
15
+ def change_group(abu)
16
+ #todo select uid or nil if personnal
17
+ # old style folders
18
+ #@uri.path -= @abu
19
+ #@uri.path += (@abu = abu)
20
+ # new style v4 group
21
+ groups = Card.where({"X-ADDRESSBOOKSERVER-KIND" => "group", "f" => abu})
22
+ @group = groups && groups.first && groups.first.uid
23
+ end
24
+
25
+ # list available address books
26
+ #find all X-ADDRESSBOOKSERVER-KIND
27
+ def list(name)
28
+ #Card.where("X-ADDRESSBOOKSERVER-KIND" => "group")
29
+ if name
30
+ query = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
31
+ #xml.send('B:addressbook-query', 'xmlns:B' => "urn:ietf:params:xml:ns:carddav") do
32
+ xml.send('D:principal-property-search', 'xmlns:D' => "DAV:") do
33
+ xml.send('D:property-search') do
34
+ xml.send('D:prop') do
35
+ xml.send('D:displayname')
36
+ end
37
+ xml.send('D:match') do
38
+ xml << name
39
+ end
40
+ end
41
+ xml.send('D:prop') do
42
+ xml.send('C:addressbook-home-set', 'xmlns:C' => 'urn:ietf:params:xml:ns:carddav')
43
+ xml.send('D:displayname')
44
+ end
45
+
46
+ end
47
+
48
+ #end
49
+
50
+ end
51
+ else
52
+ query = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
53
+ xml.send('A:propfind', 'xmlns:A' => 'DAV:') do
54
+ xml.send('A:prop') do
55
+ xml.send('D:addressbook-home-set', 'xmlns:D' => "urn:ietf:params:xml:ns:carddav")
56
+ xml.send('D:directory-gateway', 'xmlns:D' => "urn:ietf:params:xml:ns:carddav")
57
+ xml.send('A:displayname')
58
+ xml.send('C:email-address-set', 'xmlns:C' => "http://calendarserver.org/ns/")
59
+ xml.send('A:principal-collection-set')
60
+ xml.send('A:principal-URL')
61
+ xml.send('A:resource-id')
62
+ xml.send('A:supported-report-set')
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ headers = {
69
+ "content-Type" => "text/xml; charset=\"utf-8\"",
70
+ "depth" => 0,
71
+ "brief" => "t",
72
+ "Content-Length" => "#{query.to_xml.to_s.size}"
73
+ }
74
+ content = @client.request('REPORT', @uri, nil, query.to_xml.to_s, headers)
75
+ puts content.body + "<<<<<\n\n"
76
+ xml = Nokogiri::XML(content.body)
77
+ vcards = []
78
+ xml.xpath('//C:address-data').each do |card|
79
+ vcards << Card.new(card.text)
80
+ end
81
+ return vcards
82
+ end
83
+
84
+ # find addresse resource by uid
85
+ def self.find(uid)
86
+ Card.find(self, uid)
87
+ end
88
+
89
+ # create collection : not working actually
90
+ def create_folder(name, displayname = "", description = "")
91
+ query = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
92
+ xml.send('D:mkcol', 'xmlns:D' => "DAV:", 'xmlns:C' => "urn:ietf:params:xml:ns:carddav") do
93
+ xml.send('D:set') do
94
+ xml.send('D:prop')
95
+ xml.send('D:resourcetype') do
96
+ xml.send('D:collection')
97
+ xml.send('C:addressbook')
98
+ end
99
+ end
100
+ xml.send('D:displayname') do
101
+ xml << displayname
102
+ end
103
+ xml.send('C:addressbook-description', 'xml:lang' => "en") do
104
+ xml << description
105
+ end
106
+ end
107
+ end
108
+
109
+ headers = {
110
+ "content-Type" => "text/xml; charset=\"utf-8\"",
111
+ "Content-Length" => query.to_xml.to_s.size
112
+ }
113
+
114
+ res = @client.request('MKCOL', @uri, nil, query.to_xml.to_s, headers)
115
+
116
+ if res.status < 200 or res.status >= 300
117
+ raise "create failed: #{res.inspect}"
118
+ else
119
+ true
120
+ end
121
+ end
122
+
123
+ # create address book group
124
+ def create(name, description = "")
125
+ uid = "#{gen_uid}"
126
+
127
+ @vcard = Card.new(GROUPVCF)
128
+ @vcard.add_attribute("X-ADDRESSBOOKSERVER-KIND", "group")
129
+ @vcard.add_attribute("rev", Time.now.utc.iso8601(2))
130
+ @vcard.add_attribute("uid", uid)
131
+ @vcard.add_attribute(:f, name)
132
+ @vcard.add_attribute(:fn, description)
133
+
134
+ headers = {
135
+ "If-None-Match" => "*",
136
+ "Content-Type" => "text/vcard",
137
+ "Content-Length" => @vcard.to_s.size
138
+ }
139
+
140
+ unc = @uri.clone
141
+ unc.path += "#{uid}.vcf"
142
+ res = @client.request('PUT', unc, nil, @vcard.to_s, headers)
143
+
144
+ if res.status < 200 or res.status >= 300
145
+ @uid = nil
146
+ raise "create failed: #{res.inspect}"
147
+ else
148
+ @uid = @group = uid
149
+ end
150
+ @vcard
151
+
152
+ end
153
+
154
+ # access to current vcard object
155
+ def vcard
156
+ @vcard
157
+ end
158
+
159
+ def debug_dev=(dev)
160
+ @client.debug_dev = dev
161
+ end
162
+
163
+ end
164
+ end
@@ -0,0 +1,45 @@
1
+ BASEVCF = <<EOF
2
+ BEGIN:VCARD
3
+ PRODID:-//IDFuze//SimpleDav//EN
4
+ VERSION:3.0
5
+ CLASS:PUBLIC
6
+ PROFILE:VCARD
7
+ END:VCARD
8
+ EOF
9
+
10
+ GROUPVCF = BASEVCF
11
+
12
+ module SimpleDav
13
+ class Base
14
+ attr_reader :headers, :uri, :client
15
+
16
+ # generate unique id to create resources
17
+ def gen_uid
18
+ "#{rand(100000)}-#{Time.now.to_i}-#{rand(100000)}"
19
+ end
20
+
21
+ def initialize(params = {})
22
+ begin
23
+ url = params[:ssl] ? "https://#{params[:server]}/" : "http://#{params[:server]}/"
24
+ url += case (@type = params[:type])
25
+ when "sogo" then "SOGo/dav/#{params[:user]}/"
26
+ else ""
27
+ end
28
+ @uri = URI.parse(url)
29
+ @uid = nil
30
+ @headers = {}
31
+ #open(uri) if uri
32
+ proxy = ENV['HTTP_PROXY'] || ENV['http_proxy'] || nil
33
+ @client = HTTPClient.new(proxy)
34
+ @client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE unless params[:verify]
35
+ if (@user = params[:user]) && params[:pass]
36
+ @client.set_basic_auth(@uri, @user, params[:pass])
37
+ end
38
+ rescue
39
+ raise RuntimeError.exception("Init Failed!! (#{$!.to_s})")
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,205 @@
1
+ # attributes : n|email|title|nickname|tel|bday|fn|org|note|uid
2
+ # todo change for another vcard managment class
3
+ module SimpleDav
4
+ class Card
5
+ class << self; attr_accessor :adb end
6
+ @adb = nil
7
+
8
+ def initialize(text = BASEVCF)
9
+ @plain = text
10
+ return self
11
+ end
12
+
13
+ def self.find(uid)
14
+ where(:uid => uid)
15
+ end
16
+
17
+ # create address resource
18
+ def self.create(params)
19
+ @vcard = Card.new
20
+ params.each do |k,v|
21
+ @vcard.update_attribute(k,v)
22
+ end
23
+
24
+ headers = {
25
+ "If-None-Match" => "*",
26
+ "Content-Type" => "text/vcard",
27
+ "Content-Length" => @vcard.to_s.size
28
+ }
29
+ uid = "#{adb.gen_uid}.vcf"
30
+
31
+ @vcard.update_attribute(:uid, uid)
32
+ if adb && adb.group
33
+ @vcard.add_attribute("X-ADDRESSBOOKSERVER-MEMBER", "urn:uuid:#{adb.group}")
34
+ end
35
+
36
+ unc = adb.uri.clone
37
+ unc.path += uid
38
+ res = adb.client.request('PUT', unc, nil, @vcard.to_s, headers)
39
+
40
+ if res.status < 200 or res.status >= 300
41
+ @uid = nil
42
+ raise "create failed: #{res.inspect}"
43
+ else
44
+ @uid = uid
45
+ end
46
+ @vcard
47
+ end
48
+
49
+ def update(params)
50
+ params.each do |k,v|
51
+ update_attribute(k,v)
52
+ end
53
+
54
+ headers = {
55
+ "Content-Type" => "text/vcard",
56
+ "Content-Length" => @plain.size
57
+ }
58
+ uid = self.uid
59
+
60
+ unc = Card.adb.uri.clone
61
+ unc.path += uid
62
+ res = Card.adb.client.request('PUT', unc, nil, @plain, headers)
63
+
64
+ if res.status < 200 or res.status >= 300
65
+ @uid = nil
66
+ raise "create failed: #{res.inspect}"
67
+ else
68
+ @uid = uid
69
+ end
70
+ self
71
+ end
72
+
73
+ def delete
74
+ if @uid && Card.adb
75
+
76
+ headers = {
77
+ #"If-None-Match" => "*",
78
+ "Content-Type" => "text/xml; charset=\"utf-8\""
79
+ }
80
+ unc = adb.uri.clone
81
+ unc.path += @uid
82
+ res = adb.client.request('DELETE', unc, nil, nil, headers)
83
+
84
+ if res.status < 200 or res.status >= 300
85
+ @uid = nil
86
+ raise "delete failed: #{res.inspect}"
87
+ else
88
+ @uid = nil
89
+ true
90
+ end
91
+ else
92
+ raise "Failed : no connection or null id"
93
+ end
94
+ end
95
+
96
+ def retreive
97
+ path = "#{self.uid}.vcf"
98
+ unc = adb.uri.clone
99
+ unc.path += path
100
+ res = adb.client.request('GET', unc)
101
+
102
+ if res.status < 200 or res.status >= 300
103
+ @uid = nil
104
+ raise "delete failed: #{res.inspect}"
105
+ else
106
+ @plain = res.body
107
+ @uid = uid
108
+ true
109
+ end
110
+ end
111
+
112
+ def update_attribute(a, v)
113
+ @plain.match(/^#{a.to_s.upcase}:(.+)$/) ? @plain.gsub!(/^#{a.to_s.upcase}:(.+)$/, "#{a.to_s.upcase}:#{v}") : add_attribute(a, v)
114
+ end
115
+
116
+ def add_attribute(a, v)
117
+ @plain["END:VCARD"] = "#{a.to_s.upcase}:#{v}\nEND:VCARD"
118
+ end
119
+
120
+ def method_missing(meth, *args, &block)
121
+ case meth.to_s
122
+ when /^((n|email|title|nickname|tel|bday|fn|org|note|uid|X-ADDRESSBOOKSERVER-KIND)=?)$/
123
+ run_on_field($1, *args, &block)
124
+ when /^find_by_(.+)$/
125
+ run_find_by_method($1, *args, &block)
126
+ else
127
+ super
128
+ end
129
+ end
130
+
131
+ def run_on_field(attrs, *args, &block)
132
+ field = attrs.upcase
133
+ field["EMAIL"] = "EMAIL;TYPE=work" if field.match("EMAIL")
134
+
135
+ if field =~ /=/
136
+ field = field[0..-2]
137
+ update_attribute(field, args)
138
+ else
139
+ if m = @plain.match(/#{field}:(.+)$/)
140
+ m[1]
141
+ else
142
+ nil
143
+ end
144
+ end
145
+ end
146
+
147
+ # find where RoR style
148
+ def self.where(conditions)
149
+ limit = nil #1
150
+ query = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
151
+ xml.send('B:addressbook-query', 'xmlns:B' => "urn:ietf:params:xml:ns:carddav") do
152
+ xml.send('A:prop', 'xmlns:A' => "DAV:") do
153
+ xml.send('A:getetag')
154
+ xml.send('B:address-data')
155
+
156
+ end
157
+ #xml.send('C:filter', 'test' => "anyof") do
158
+ xml.send('B:filter', 'test' => 'anyof') do
159
+ conditions.each do |k,v|
160
+ xml.send('B:prop-filter', 'test' => 'allof','name' => k.to_s) do
161
+ #xml.send('C:text-match', 'collation' => "i;unicode-casemap", 'match-type' => "contains") do
162
+ xml.send('B:text-match', 'collation' => "i;unicode-casemap", 'match-type' => "contains") do
163
+ xml << v
164
+ end
165
+ end
166
+ end
167
+ end
168
+ if limit
169
+ xml.send('C:limit') do
170
+ xml.send('C:nresults') do
171
+ xml << "#{limit}"
172
+ end
173
+ end
174
+ end
175
+
176
+ end
177
+
178
+ end
179
+ headers = {
180
+ "content-Type" => "text/xml; charset=\"utf-8\"",
181
+ "depth" => 1,
182
+ "Content-Length" => "#{query.to_xml.to_s.size}"
183
+ }
184
+ content = adb.client.request('REPORT', adb.uri, nil, query.to_xml.to_s, headers)
185
+ xml = Nokogiri::XML(content.body)
186
+ vcards = []
187
+ xml.xpath('//C:address-data').each do |card|
188
+ vcards << Card.new(card.text)
189
+ end
190
+ return vcards
191
+ end
192
+
193
+ def run_find_by_method(attrs, *args, &block)
194
+ attrs = attrs.split('_and_')
195
+ attrs_with_args = [attrs, args].transpose
196
+ conditions = Hash[attrs_with_args]
197
+ where(conditions)
198
+ end
199
+
200
+ def to_s
201
+ @plain.to_s
202
+ end
203
+
204
+ end
205
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 5
9
- version: 0.0.5
8
+ - 6
9
+ version: 0.0.6
10
10
  platform: ruby
11
11
  authors:
12
12
  - Olivier DIRRENBERGER
@@ -54,7 +54,12 @@ extensions: []
54
54
  extra_rdoc_files: []
55
55
 
56
56
  files:
57
+ - lib/simple_dav/address_book.rb
58
+ - lib/simple_dav/base.rb
59
+ - lib/simple_dav/card.rb
57
60
  - lib/simple_dav.rb
61
+ - README
62
+ - TODO
58
63
  has_rdoc: true
59
64
  homepage: https://github.com/reivilo/simple_dav
60
65
  licenses: []