adobeshare 0.0.2
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/History.txt +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +12 -0
- data/README.txt +70 -0
- data/Rakefile +4 -0
- data/examples/config.yaml.sample +5 -0
- data/examples/sample.rb +114 -0
- data/lib/adobeshare.rb +9 -0
- data/lib/adobeshare/client.rb +303 -0
- data/lib/adobeshare/node.rb +54 -0
- data/lib/adobeshare/version.rb +9 -0
- data/setup.rb +1585 -0
- data/test/test_adobeshare.rb +11 -0
- data/test/test_helper.rb +2 -0
- metadata +70 -0
data/History.txt
ADDED
data/License.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 KATO Hideyuki
|
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/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
= AdobeShare::Client -- Adobe Share API for Ruby
|
2
|
+
|
3
|
+
AdobeShare::Client is a library for upload and download documents using REST APIs for Adobe Share.
|
4
|
+
|
5
|
+
* http://labs.adobe.com/technologies/share/
|
6
|
+
* http://labs.adobe.com/wiki/index.php/Share:API
|
7
|
+
|
8
|
+
== Example
|
9
|
+
|
10
|
+
=== Create client class
|
11
|
+
|
12
|
+
client = AdobeShare::Client.new
|
13
|
+
if you want to access via proxy,
|
14
|
+
add proxy param Adobe::Client.new "http://your.proxy.server:8080/"
|
15
|
+
|
16
|
+
=== Set credentials
|
17
|
+
|
18
|
+
client.apikey = Your API Key
|
19
|
+
client.secret = Your Shared Secret
|
20
|
+
client.username = Your Adobe ID
|
21
|
+
client.password = Your Adobe ID passowrd
|
22
|
+
|
23
|
+
=== Login
|
24
|
+
|
25
|
+
client.login
|
26
|
+
|
27
|
+
=== Access your Adobe Share Documents
|
28
|
+
|
29
|
+
==== Upload
|
30
|
+
|
31
|
+
client.add_file `binary obj`, "test.pdf", "This is Test File"
|
32
|
+
|
33
|
+
==== Download
|
34
|
+
|
35
|
+
client.get_source `nodeid`
|
36
|
+
|
37
|
+
==== Delete
|
38
|
+
|
39
|
+
client.delete_node `nodeid`
|
40
|
+
|
41
|
+
|
42
|
+
# `nodeid` is the same as `docid` in the Flash web interface.
|
43
|
+
# `nodeid` indicates both the documents and the folders. Maybe.
|
44
|
+
|
45
|
+
=== Logout
|
46
|
+
client.logout
|
47
|
+
|
48
|
+
For details, please see examples/sample.rb
|
49
|
+
|
50
|
+
== TODO
|
51
|
+
|
52
|
+
* Test::Unit !!!
|
53
|
+
|
54
|
+
and, these APIs are not implemented.
|
55
|
+
* Moving or renaming a file or folder
|
56
|
+
* Sharing a file
|
57
|
+
* Adding or removing recipients from a share
|
58
|
+
|
59
|
+
== Requirements
|
60
|
+
|
61
|
+
* httpclient
|
62
|
+
* rubygems
|
63
|
+
|
64
|
+
== Installation
|
65
|
+
|
66
|
+
* gem install adobeshare
|
67
|
+
|
68
|
+
== Author
|
69
|
+
|
70
|
+
* KATO Hideyuki <hideyuki at kato dot jp>
|
data/Rakefile
ADDED
data/examples/sample.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'adobeshare'
|
4
|
+
require 'yaml'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
class SampleClient
|
8
|
+
def initialize
|
9
|
+
config = YAML.load_file("config.yaml")
|
10
|
+
@client = AdobeShare::Client.new
|
11
|
+
@client.apikey = config["apikey"]
|
12
|
+
@client.secret = config["secret"]
|
13
|
+
@client.username = config["username"]
|
14
|
+
@client.password = config["password"]
|
15
|
+
end
|
16
|
+
|
17
|
+
def login
|
18
|
+
puts "=== Login ==="
|
19
|
+
@client.login
|
20
|
+
puts "sessionid : #{@client.sessionid}"
|
21
|
+
#puts "secret : #{@client.secret}"
|
22
|
+
puts "name : #{@client.name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def logout
|
26
|
+
puts "=== Logout ==="
|
27
|
+
@client.logout
|
28
|
+
end
|
29
|
+
|
30
|
+
def list nodeid=nil
|
31
|
+
puts "=== Contents List ==="
|
32
|
+
node = @client.get_node
|
33
|
+
children = node[:children]
|
34
|
+
children.each{|e|
|
35
|
+
puts "#{e[:nodeid]} : #{e[:name]}" if e[:hascontent]
|
36
|
+
}
|
37
|
+
puts ""
|
38
|
+
puts children.size.to_s + " documents(or folders)"
|
39
|
+
puts " in this folder (nodeid=#{node[:nodeid]})"
|
40
|
+
end
|
41
|
+
|
42
|
+
def info nodeid
|
43
|
+
puts "=== Content Info ==="
|
44
|
+
node = @client.get_node nodeid
|
45
|
+
pp node
|
46
|
+
end
|
47
|
+
|
48
|
+
def upload filename
|
49
|
+
list
|
50
|
+
puts "=== Upload ==="
|
51
|
+
return unless filename
|
52
|
+
node = @client.get_node
|
53
|
+
data = nil
|
54
|
+
File.open(filename, "rb"){|f|
|
55
|
+
data = f.read
|
56
|
+
}
|
57
|
+
puts "Uploading file `#{filename}` ..."
|
58
|
+
@client.add_file data, File.basename(filename), "Test File", node[:nodeid]
|
59
|
+
list
|
60
|
+
end
|
61
|
+
|
62
|
+
def download nodeid
|
63
|
+
puts "=== File Download ==="
|
64
|
+
node = @client.get_node nodeid
|
65
|
+
pp node
|
66
|
+
|
67
|
+
if node[:hascontent]
|
68
|
+
filename = node[:name]
|
69
|
+
puts "Downloading file `#{filename}` ..."
|
70
|
+
data = @client.get_source node[:nodeid]
|
71
|
+
File.open(filename, "wb"){|f|
|
72
|
+
f.write(data)
|
73
|
+
}
|
74
|
+
if node[:thumbnailstate] == "1"
|
75
|
+
thumbnail = node[:name] + ".thumb.jpg"
|
76
|
+
puts "Downloading file `#{thumbnail}` ..."
|
77
|
+
data = @client.get_thumbnail node[:nodeid]
|
78
|
+
File.open(thumbnail, "wb"){|f|
|
79
|
+
f.write(data)
|
80
|
+
}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def delete nodeid
|
86
|
+
node = info nodeid
|
87
|
+
puts "=== File Delete ==="
|
88
|
+
puts ""
|
89
|
+
print "Are you OK? [yN]"
|
90
|
+
ans = STDIN.getc.chr.downcase
|
91
|
+
return if ans != 'y'
|
92
|
+
@client.delete_node nodeid
|
93
|
+
list
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
if __FILE__ == $0
|
98
|
+
sample = SampleClient.new
|
99
|
+
if ARGV[0]
|
100
|
+
command = ARGV[0]
|
101
|
+
param = ARGV[1]
|
102
|
+
sample.login
|
103
|
+
sample.send(command, param)
|
104
|
+
sample.logout
|
105
|
+
else
|
106
|
+
puts "Usage: #{$0} command param"
|
107
|
+
puts "commands:"
|
108
|
+
puts " list"
|
109
|
+
puts " info `nodeid`"
|
110
|
+
puts " upload `filename`"
|
111
|
+
puts " download `nodeid`"
|
112
|
+
puts " delete `nodeid`"
|
113
|
+
end
|
114
|
+
end
|
data/lib/adobeshare.rb
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
#
|
2
|
+
# Adobe Share API for Ruby
|
3
|
+
#
|
4
|
+
# Copyright (c) KATO Hideyuki, 2007. All rights reserved.
|
5
|
+
#
|
6
|
+
require 'http-access2'
|
7
|
+
require 'rexml/document'
|
8
|
+
|
9
|
+
include REXML
|
10
|
+
|
11
|
+
module AdobeShare
|
12
|
+
DEFAULT_ENDPOINT = "https://api.share.adobe.com/webservices/api/v1/" # :nodoc:
|
13
|
+
ENDPOINT_AUTH = DEFAULT_ENDPOINT + "auth/" # :nodoc:
|
14
|
+
ENDPOINT_SESSIONS = DEFAULT_ENDPOINT + "sessions/" # :nodoc:
|
15
|
+
ENDPOINT_DC = DEFAULT_ENDPOINT + "dc/" # :nodoc:
|
16
|
+
|
17
|
+
#
|
18
|
+
# Client Access Class
|
19
|
+
#
|
20
|
+
class Client
|
21
|
+
# User-Agent
|
22
|
+
UA = "AdobeShare::Client-#{VERSION::STRING}" # :nodoc:
|
23
|
+
|
24
|
+
# User's API Key:
|
25
|
+
attr_accessor :apikey
|
26
|
+
# User's session shared secret
|
27
|
+
attr_accessor :secret
|
28
|
+
# User's Adobe ID
|
29
|
+
attr_accessor :username
|
30
|
+
# User's password
|
31
|
+
attr_accessor :password
|
32
|
+
# User's name
|
33
|
+
attr_reader :name
|
34
|
+
# User's session ID
|
35
|
+
attr_reader :sessionid
|
36
|
+
|
37
|
+
#
|
38
|
+
def initialize proxy=nil
|
39
|
+
@client = HTTPAccess2::Client.new(proxy)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Login to use Adobe Share service
|
43
|
+
def login
|
44
|
+
authtoken = get_authtoken
|
45
|
+
start_sessions authtoken
|
46
|
+
end
|
47
|
+
|
48
|
+
# Logout from the servce
|
49
|
+
def logout
|
50
|
+
end_sessions
|
51
|
+
end
|
52
|
+
|
53
|
+
# Get a list of nodes (files and folders) in a user's account
|
54
|
+
def get_node(nodeid=nil)
|
55
|
+
if nodeid
|
56
|
+
uri = ENDPOINT_DC + nodeid + "/"
|
57
|
+
else
|
58
|
+
uri = ENDPOINT_DC
|
59
|
+
end
|
60
|
+
res = http_client_request("GET", uri)
|
61
|
+
node = Node.new res
|
62
|
+
node.to_hash
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get the source of a document
|
66
|
+
def get_source nodeid
|
67
|
+
get_node_content nodeid, RENDITION_SOURCE
|
68
|
+
end
|
69
|
+
|
70
|
+
# Get the thumbnail of a document
|
71
|
+
def get_thumbnail nodeid
|
72
|
+
get_node_content nodeid, RENDITION_THUMBNAIL
|
73
|
+
end
|
74
|
+
|
75
|
+
# Get the specified rendition of a document
|
76
|
+
def get_node_content nodeid, rendition
|
77
|
+
uri = ENDPOINT_DC + nodeid + "/" + rendition + "/"
|
78
|
+
res = http_client_request("GET", uri)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Delete a node
|
82
|
+
def delete_node nodeid
|
83
|
+
uri = ENDPOINT_DC + nodeid + "/"
|
84
|
+
res = http_client_request("DELETE", uri)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Upload and add file
|
88
|
+
def add_file(data, filename, description, nodeid="", renditions=true)
|
89
|
+
uri = ENDPOINT_DC + nodeid + "/"
|
90
|
+
|
91
|
+
# request xml
|
92
|
+
req_xml = Element.new "request"
|
93
|
+
file = req_xml.add_element "file"
|
94
|
+
file.add_element "name"
|
95
|
+
file.add_element "description"
|
96
|
+
file.add_element "renditions"
|
97
|
+
file.elements["name"].text = filename
|
98
|
+
file.elements["description"].text = description
|
99
|
+
file.elements["renditions"].text = renditions
|
100
|
+
# FIXME
|
101
|
+
# XXX Is there any good library or something ???
|
102
|
+
# to make multipart/form-data ...
|
103
|
+
boundary = "mime-part-separator" + Time.new.to_i.to_s
|
104
|
+
req = "--#{boundary}\r\n"
|
105
|
+
req << "Content-Disposition: form-data; name=\"request\"\r\n"
|
106
|
+
req << "\r\n"
|
107
|
+
req << req_xml.to_s
|
108
|
+
req << "\r\n"
|
109
|
+
req << "--#{boundary}\r\n"
|
110
|
+
req << "Content-Disposition: form-data; name=\"file\"; filename=\"#{URI.encode(filename)}\"\r\n"
|
111
|
+
req << "Content-Type: application/octet-stream\r\n"
|
112
|
+
req << "Content-Transfer-Encoding: binary\r\n"
|
113
|
+
req << "\r\n"
|
114
|
+
req << data
|
115
|
+
req << "\r\n"
|
116
|
+
req << "--#{boundary}--\r\n"
|
117
|
+
|
118
|
+
headers = Hash.new
|
119
|
+
headers["Content-Type"] = "multipart/form-data; boundary=#{boundary}"
|
120
|
+
res = http_client_request("POST", uri, req, headers)
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
# TODO this method is not tested
|
126
|
+
# Rename a node
|
127
|
+
def rename_node(nodeid, newname)
|
128
|
+
uri = ENDPOINT_DC + nodeid + "/?newname=" + newname
|
129
|
+
res = http_client_request("MOVE", uri)
|
130
|
+
end
|
131
|
+
|
132
|
+
# TODO this method is not tested
|
133
|
+
# Move a node
|
134
|
+
def move_node(nodeid, destnodeid, newname)
|
135
|
+
uri = ENDPOINT_DC + nodeid + "/?destnodeid=" + destnodeid + "&newname=" + URI.encode(newname)
|
136
|
+
res = http_client_request("MOVE", uri)
|
137
|
+
end
|
138
|
+
|
139
|
+
# TODO this method is not tested
|
140
|
+
# Replace file in an already existing node
|
141
|
+
def replace_file(file_name, description, nodeid, renditions=true)
|
142
|
+
add_file(file_name, description, nodeid, renditions)
|
143
|
+
end
|
144
|
+
|
145
|
+
# TODO this method is not tested
|
146
|
+
# Add new folder
|
147
|
+
def add_folder(name, description, nodeid)
|
148
|
+
if nodeid
|
149
|
+
uri = ENDPOINT_DC + nodeid + "/"
|
150
|
+
else
|
151
|
+
uri = ENDPOINT_DC
|
152
|
+
end
|
153
|
+
req = Element.new "request"
|
154
|
+
folder = req.add_element "folder"
|
155
|
+
folder.add_element "name"
|
156
|
+
folder.add_element "description"
|
157
|
+
folder.elements["name"].text = name
|
158
|
+
folder.elements["description"].text = description
|
159
|
+
res = http_client_request("POST", uri, req)
|
160
|
+
doc = Document.new res
|
161
|
+
end
|
162
|
+
|
163
|
+
# TODO this method is not tested
|
164
|
+
# Share a file
|
165
|
+
def share_file(nodeid, users, message, level)
|
166
|
+
uri = ENDPOINT_DC + nodeid + "/share/"
|
167
|
+
req = Element.new "request"
|
168
|
+
share = req.add_element "share"
|
169
|
+
users.each{|e|
|
170
|
+
user = Element.new "user"
|
171
|
+
user.text = e
|
172
|
+
share.add_element user
|
173
|
+
}
|
174
|
+
req.add_element "message"
|
175
|
+
req.add_element "level"
|
176
|
+
req.elements["message"].text = message
|
177
|
+
req.elements["level"].text = level
|
178
|
+
res = http_client_request("POST", uri, req)
|
179
|
+
doc = Document.new res
|
180
|
+
end
|
181
|
+
|
182
|
+
# TODO this method is not tested
|
183
|
+
# Unshare a file
|
184
|
+
def unshare_file(nodeid)
|
185
|
+
share_file(nodeid, nil, nil, 0)
|
186
|
+
end
|
187
|
+
|
188
|
+
# TODO this method is not tested
|
189
|
+
# Update a share file
|
190
|
+
def update_share(nodeid, users_to_add, users_to_remove, message)
|
191
|
+
uri = ENDPOINT_DC + nodeid + "/share/"
|
192
|
+
req = Element.new "request"
|
193
|
+
share = req.add_element "share"
|
194
|
+
users_to_add.each{|e|
|
195
|
+
user = Element.new "user"
|
196
|
+
user.text = e
|
197
|
+
share.add_element user
|
198
|
+
}
|
199
|
+
unshare = req.add_element "unshare"
|
200
|
+
users_to_remove.each{|e|
|
201
|
+
user = Element.new "user"
|
202
|
+
user.text = e
|
203
|
+
unshare.add_element user
|
204
|
+
}
|
205
|
+
req.add_element "message"
|
206
|
+
req.add_element "level"
|
207
|
+
req.elements["message"].text = message
|
208
|
+
res = http_client_request("POST", uri, req)
|
209
|
+
doc = Document.new res
|
210
|
+
end
|
211
|
+
|
212
|
+
private
|
213
|
+
|
214
|
+
# Requesting an authorization token
|
215
|
+
def get_authtoken
|
216
|
+
raise ParameterError unless @username && @password
|
217
|
+
uri = ENDPOINT_AUTH
|
218
|
+
req = Element.new "request"
|
219
|
+
req.add_element "username"
|
220
|
+
req.add_element "password"
|
221
|
+
req.elements["username"].text = username
|
222
|
+
req.elements["password"].text = password
|
223
|
+
res = http_client_request("POST", uri, req)
|
224
|
+
doc = Document.new res
|
225
|
+
authtoken = doc.elements['/response/authtoken/'].text
|
226
|
+
authtoken
|
227
|
+
end
|
228
|
+
|
229
|
+
# Starting a new session
|
230
|
+
def start_sessions authtoken
|
231
|
+
uri = ENDPOINT_SESSIONS
|
232
|
+
req = Element.new "request"
|
233
|
+
req.add_element "authtoken"
|
234
|
+
req.elements["authtoken"].text = authtoken
|
235
|
+
res = http_client_request("POST", uri, req)
|
236
|
+
doc = Document.new res
|
237
|
+
@sessionid = doc.elements['/response/sessionid/'].text
|
238
|
+
@secret = doc.elements['/response/secret'].text
|
239
|
+
@name = doc.elements['/response/name'].text
|
240
|
+
end
|
241
|
+
|
242
|
+
# Ending a session
|
243
|
+
def end_sessions
|
244
|
+
uri = ENDPOINT_SESSIONS + @sessionid +'/'
|
245
|
+
res = http_client_request("DELETE", uri)
|
246
|
+
@sessionid = nil
|
247
|
+
@secret = nil
|
248
|
+
end
|
249
|
+
|
250
|
+
# Make HTTP Headers (Authorization)
|
251
|
+
def make_headers(method, uri)
|
252
|
+
raise ParameterError unless @apikey && @secret
|
253
|
+
time = Time.new
|
254
|
+
calltime = time.to_i.to_s + sprintf("%06d", time.tv_usec)
|
255
|
+
data = "#{method} #{uri} #{calltime}"
|
256
|
+
sig = Digest::MD5.hexdigest(data + @secret)
|
257
|
+
headers = Hash.new
|
258
|
+
auth_string = "AdobeAuth "
|
259
|
+
auth_string << "sessionid=\"#{@sessionid}\"," if @sessionid
|
260
|
+
auth_string << "apikey=\"#{@apikey}\",data=\"#{data}\",sig=\"#{sig}\""
|
261
|
+
headers["Authorization"] = auth_string
|
262
|
+
headers["User-Agent"] = UA
|
263
|
+
headers
|
264
|
+
end
|
265
|
+
|
266
|
+
# Performs a generic HTTP request.
|
267
|
+
def http_client_request(method, uri_s, request=nil, header_add={})
|
268
|
+
uri_s << "?method=MOVE" if method == "MOVE"
|
269
|
+
uri = URI.parse uri_s
|
270
|
+
headers = make_headers method, uri
|
271
|
+
headers = headers.merge header_add
|
272
|
+
case method
|
273
|
+
when "GET"
|
274
|
+
res = @client.get(uri, nil, headers)
|
275
|
+
when "POST"
|
276
|
+
res = @client.post(uri, request, headers)
|
277
|
+
when "PUT"
|
278
|
+
res = @client.put(uri, request, headers)
|
279
|
+
when "DELETE"
|
280
|
+
res = @client.delete(uri, headers)
|
281
|
+
when "MOVE"
|
282
|
+
res = @client.post(uri, request, headers)
|
283
|
+
end
|
284
|
+
body = res.body.content
|
285
|
+
status_code = res.header.response_status_code
|
286
|
+
reason_phrase = res.header.reason_phrase
|
287
|
+
if status_code >= 500
|
288
|
+
raise ServerError, "#{status_code} #{reason_phrase}"
|
289
|
+
elsif status_code >= 400
|
290
|
+
body = res.body.content
|
291
|
+
raise RequestError, "#{status_code} #{reason_phrase} (#{body})"
|
292
|
+
end
|
293
|
+
body
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
class ServerError < RuntimeError
|
298
|
+
end
|
299
|
+
class RequestError < RuntimeError
|
300
|
+
end
|
301
|
+
class ParameterError < RuntimeError
|
302
|
+
end
|
303
|
+
end
|