sndacs 0.0.1 → 0.1.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/lib/sndacs/object.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Sndacs
2
2
 
3
- # Class responsible for handling objects stored in S3 buckets
3
+ # Class responsible for handling objects stored in CS buckets
4
4
  class Object
5
5
  include Parser
6
6
  extend Forwardable
@@ -10,7 +10,7 @@ module Sndacs
10
10
  attr_writer :content
11
11
 
12
12
  def_instance_delegators :bucket, :name, :service, :bucket_request, :vhost?, :host, :path_prefix
13
- def_instance_delegators :service, :protocol, :port, :secret_access_key
13
+ def_instance_delegators :service, :protocol, :port, :access_key_id, :secret_access_key
14
14
  private_class_method :new
15
15
 
16
16
  # Compares the object with other object. Returns true if the key
@@ -57,30 +57,24 @@ module Sndacs
57
57
  # to do it).
58
58
  def retrieve
59
59
  object_headers
60
- self
61
- end
62
60
 
63
- # Retrieves the object from the server, returns true if the object
64
- # exists or false otherwise. Uses #retrieve method, but catches
65
- # S3::Error::NoSuchKey exception and returns false when it happens
66
- def exists?
67
- retrieve
68
- true
69
- rescue Error::NoSuchKey
70
- false
61
+ self
71
62
  end
72
63
 
73
64
  # Downloads the content of the object, and caches it. Pass true to
74
65
  # clear the cache and download the object again.
75
66
  def content(reload = false)
76
67
  return @content if defined?(@content) and not reload
68
+
77
69
  get_object
70
+
78
71
  @content
79
72
  end
80
73
 
81
74
  # Saves the object, returns true if successfull.
82
75
  def save
83
76
  put_object
77
+
84
78
  true
85
79
  end
86
80
 
@@ -89,7 +83,7 @@ module Sndacs
89
83
  # ==== Options
90
84
  # * <tt>:key</tt> - New key to store object in
91
85
  # * <tt>:bucket</tt> - New bucket to store object in (instance of
92
- # S3::Bucket)
86
+ # Sndacs::Bucket)
93
87
  # * <tt>:acl</tt> - ACL of the copied object (default:
94
88
  # "public-read")
95
89
  # * <tt>:content_type</tt> - Content type of the copied object
@@ -101,40 +95,53 @@ module Sndacs
101
95
  # Destroys the file on the server
102
96
  def destroy
103
97
  delete_object
98
+
104
99
  true
105
100
  end
106
101
 
107
102
  # Returns Object's URL using protocol specified in service,
108
- # e.g. <tt>http://domain.com.s3.amazonaws.com/key/with/path.extension</tt>
103
+ # e.g. <tt>http://storage.grandcloud.cn/bucket/key/file.extension</tt>
109
104
  def url
110
105
  URI.escape("#{protocol}#{host}/#{path_prefix}#{key}")
111
106
  end
112
107
 
108
+ # Returns Object's CNAME URL (without <tt>storage.grandcloud.cn</tt>
109
+ # suffix) using protocol specified in Service,
110
+ # e.g. <tt>http://domain.com/key/with/path.extension</tt>. (you
111
+ # have to set the CNAME in your DNS before using the CNAME URL
112
+ # schema).
113
+ def cname_url
114
+ URI.escape("#{protocol}#{name}/#{key}") if bucket.vhost?
115
+ end
116
+
113
117
  # Returns a temporary url to the object that expires on the
114
- # timestamp given. Defaults to one hour expire time.
118
+ # timestamp given. Defaults to 5min expire time.
115
119
  def temporary_url(expires_at = Time.now + 3600)
120
+ url = URI.escape("#{protocol}#{host(true)}/#{path_prefix}#{key}")
116
121
  signature = Signature.generate_temporary_url_signature(:bucket => name,
117
122
  :resource => key,
118
123
  :expires_at => expires_at,
119
124
  :secret_access_key => secret_access_key)
120
125
 
121
- "#{url}?AWSAccessKeyId=#{self.bucket.service.access_key_id}&Expires=#{expires_at.to_i.to_s}&Signature=#{signature}"
126
+ "#{url}?SNDAAccessKeyId=#{access_key_id}&Expires=#{expires_at.to_i.to_s}&Signature=#{signature}"
122
127
  end
123
128
 
124
- # Returns Object's CNAME URL (without <tt>s3.amazonaws.com</tt>
125
- # suffix) using protocol specified in Service,
126
- # e.g. <tt>http://domain.com/key/with/path.extension</tt>. (you
127
- # have to set the CNAME in your DNS before using the CNAME URL
128
- # schema).
129
- def cname_url
130
- URI.escape("#{protocol}#{name}/#{key}") if bucket.vhost?
129
+ # Retrieves the object from the server, returns true if the object
130
+ # exists or false otherwise. Uses #retrieve method, but catches
131
+ # Sndacs::Error::NoSuchKey exception and returns false when it happens
132
+ def exists?
133
+ retrieve
134
+
135
+ true
136
+ rescue Error::NoSuchKey
137
+ false
131
138
  end
132
139
 
133
140
  def inspect #:nodoc:
134
141
  "#<#{self.class}:/#{name}/#{key}>"
135
142
  end
136
143
 
137
- private
144
+ private
138
145
 
139
146
  attr_writer :last_modified, :etag, :size, :original_key, :bucket
140
147
 
@@ -171,11 +178,13 @@ module Sndacs
171
178
 
172
179
  def get_object(options = {})
173
180
  response = object_request(:get, options)
181
+
174
182
  parse_headers(response)
175
183
  end
176
184
 
177
185
  def object_headers(options = {})
178
186
  response = object_request(:head, options)
187
+
179
188
  parse_headers(response)
180
189
  rescue Error::ResponseError => e
181
190
  if e.response.code.to_i == 404
@@ -187,6 +196,7 @@ module Sndacs
187
196
 
188
197
  def put_object
189
198
  response = object_request(:put, :body => content, :headers => dump_headers)
199
+
190
200
  parse_headers(response)
191
201
  end
192
202
 
@@ -250,4 +260,5 @@ module Sndacs
250
260
  end
251
261
  end
252
262
  end
263
+
253
264
  end
@@ -1,4 +1,5 @@
1
1
  module Sndacs
2
+
2
3
  module ObjectsExtension
3
4
  # Builds the object in the bucket with given key
4
5
  def build(key)
@@ -34,4 +35,5 @@ module Sndacs
34
35
  proxy_target.each { |object| object.destroy }
35
36
  end
36
37
  end
38
+
37
39
  end
data/lib/sndacs/parser.rb CHANGED
@@ -1,25 +1,36 @@
1
1
  require 'rexml/document'
2
2
 
3
3
  module Sndacs
4
+
4
5
  module Parser
5
6
  include REXML
6
7
 
7
8
  def rexml_document(xml)
8
9
  xml.force_encoding(::Encoding::UTF_8) if xml.respond_to? :force_encoding
10
+
9
11
  Document.new(xml)
10
12
  end
11
13
 
12
- def parse_list_all_my_buckets_result(xml)
13
- names = []
14
- rexml_document(xml).elements.each("ListAllMyBucketsResult/Buckets/Bucket/Name") { |e| names << e.text }
15
- names
16
- end
14
+ def parse_all_buckets_result(xml)
15
+ all_buckets = []
16
+ rexml_document(xml).elements.each("ListAllMyBucketsResult/Buckets/Bucket") do |e|
17
+ bucket_attributes = {}
18
+ bucket_attributes[:name] = e.elements["Name"].text
17
19
 
18
- def parse_location_constraint(xml)
19
- rexml_document(xml).elements["LocationConstraint"].text
20
+ element_location = e.elements["Location"]
21
+ if element_location
22
+ bucket_attributes[:location] = element_location.text
23
+ else
24
+ bucket_attributes[:location] = REGION_DEFAULT
25
+ end
26
+
27
+ all_buckets << bucket_attributes
28
+ end
29
+
30
+ all_buckets
20
31
  end
21
32
 
22
- def parse_list_bucket_result(xml)
33
+ def parse_all_objects_result(xml)
23
34
  objects_attributes = []
24
35
  rexml_document(xml).elements.each("ListBucketResult/Contents") do |e|
25
36
  object_attributes = {}
@@ -27,11 +38,22 @@ module Sndacs
27
38
  object_attributes[:etag] = e.elements["ETag"].text
28
39
  object_attributes[:last_modified] = e.elements["LastModified"].text
29
40
  object_attributes[:size] = e.elements["Size"].text
41
+
30
42
  objects_attributes << object_attributes
31
43
  end
44
+
32
45
  objects_attributes
33
46
  end
34
47
 
48
+ def parse_location_constraint(xml)
49
+ location = rexml_document(xml).elements["LocationConstraint"].text
50
+ if location.nil? || location == ''
51
+ location = REGION_DEFAULT
52
+ end
53
+
54
+ location
55
+ end
56
+
35
57
  def parse_copy_object_result(xml)
36
58
  object_attributes = {}
37
59
  document = rexml_document(xml)
@@ -51,4 +73,5 @@ module Sndacs
51
73
  rexml_document(xml).elements["ListBucketResult/IsTruncated"].text =='true'
52
74
  end
53
75
  end
76
+
54
77
  end
@@ -1,25 +1,31 @@
1
1
  module Sndacs
2
+
2
3
  # Class responsible for sending chunked requests
3
4
  # properly. Net::HTTPGenericRequest has hardcoded chunk_size, so we
4
5
  # inherit the class and override chunk_size.
5
6
  class Request < Net::HTTPGenericRequest
6
7
  def initialize(chunk_size, m, reqbody, resbody, path, initheader = nil)
7
8
  @chunk_size = chunk_size
9
+
8
10
  super(m, reqbody, resbody, path, initheader)
9
11
  end
10
12
 
11
- private
13
+ private
12
14
 
13
15
  def send_request_with_body_stream(sock, ver, path, f)
14
16
  unless content_length() or chunked?
15
17
  raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'"
16
18
  end
19
+
17
20
  supply_default_content_type
21
+
18
22
  write_header sock, ver, path
23
+
19
24
  if chunked?
20
25
  while s = f.read(@chunk_size)
21
26
  sock.write(sprintf("%x\r\n", s.length) << s << "\r\n")
22
27
  end
28
+
23
29
  sock.write "0\r\n\r\n"
24
30
  else
25
31
  while s = f.read(@chunk_size)
@@ -28,4 +34,5 @@ module Sndacs
28
34
  end
29
35
  end
30
36
  end
37
+
31
38
  end
@@ -3,15 +3,16 @@ require 'net/http'
3
3
  require 'proxies'
4
4
 
5
5
  require 'sndacs/parser'
6
- require 'sndacs/buckets_extension'
7
6
  require 'sndacs/connection'
7
+ require 'sndacs/buckets_extension'
8
8
 
9
9
  module Sndacs
10
+
10
11
  class Service
11
12
  include Parser
12
13
  include Proxies
13
14
 
14
- attr_reader :access_key_id, :secret_access_key, :use_ssl, :proxy
15
+ attr_reader :access_key_id, :secret_access_key, :proxy , :use_ssl
15
16
 
16
17
  # Compares service to other, by <tt>access_key_id</tt> and
17
18
  # <tt>secret_access_key</tt>
@@ -30,28 +31,28 @@ module Sndacs
30
31
  # (false by default)
31
32
  # * <tt>:timeout</tt> - Timeout to use by the Net::HTTP object
32
33
  # (60 by default)
33
- def initialize(options)
34
- @access_key_id = options.fetch(:access_key_id)
35
- @secret_access_key = options.fetch(:secret_access_key)
36
- @use_ssl = options.fetch(:use_ssl, false)
37
- @timeout = options.fetch(:timeout, 60)
38
- @debug = options.fetch(:debug, false)
39
-
40
- raise ArgumentError, "Missing proxy settings. Must specify at least :host." if options[:proxy] && !options[:proxy][:host]
41
- @proxy = options.fetch(:proxy, nil)
34
+ def initialize(options = {})
35
+ @access_key_id = options.fetch(:access_key_id, Config.access_key_id)
36
+ @secret_access_key = options.fetch(:secret_access_key, Config.secret_access_key)
37
+ @proxy = options.fetch(:proxy, Config.proxy)
38
+ @timeout = options.fetch(:timeout, Config.timeout)
39
+ @use_ssl = options.fetch(:use_ssl, Config.use_ssl)
40
+ @debug = options.fetch(:debug, Config.debug)
41
+
42
+ raise ArgumentError, "Wrong proxy settings. Must specify at least :host option." if @proxy && !@proxy[:host]
42
43
  end
43
44
 
44
45
  # Returns all buckets in the service and caches the result (see
45
46
  # +reload+)
46
47
  def buckets
47
- Proxy.new(lambda { list_all_my_buckets }, :owner => self, :extend => BucketsExtension)
48
+ Proxy.new(lambda { buckets_all }, :owner => self, :extend => BucketsExtension)
48
49
  end
49
50
 
50
- # Returns the bucket with the given name. Does not check whether the
51
+ # Returns the bucket with the given name and region. Does not check whether the
51
52
  # bucket exists. But also does not issue any HTTP requests, so it's
52
53
  # much faster than buckets.find
53
- def bucket(name)
54
- Bucket.send(:new, self, name)
54
+ def bucket(name, region = nil)
55
+ Bucket.send(:new, self, name, region || REGION_DEFAULT)
55
56
  end
56
57
 
57
58
  # Returns "http://" or "https://", depends on <tt>:use_ssl</tt>
@@ -70,26 +71,36 @@ module Sndacs
70
71
  "#<#{self.class}:#@access_key_id>"
71
72
  end
72
73
 
73
- private
74
+ private
74
75
 
75
- def list_all_my_buckets
76
+ def buckets_all
76
77
  response = service_request(:get)
77
- names = parse_list_all_my_buckets_result(response.body)
78
- names.map { |name| Bucket.send(:new, self, name) }
78
+
79
+ all_buckets = parse_all_buckets_result(response.body)
80
+ all_buckets.map { |bucket| Bucket.send(:new, self, bucket[:name], bucket[:region]) }
79
81
  end
80
82
 
81
83
  def service_request(method, options = {})
82
- connection.request(method, options.merge(:path => "/#{options[:path]}"))
84
+ unless options[:path]
85
+ options[:path] = '/'
86
+ end
87
+
88
+ req_path = options[:path]
89
+ if req_path[0,1] != '/'
90
+ req_path = "/#{req_path}"
91
+ end
92
+
93
+ connection.request(method, options.merge(:path => req_path))
83
94
  end
84
95
 
85
96
  def connection
86
- return @connection if defined?(@connection)
87
- @connection = Connection.new(:access_key_id => @access_key_id,
88
- :secret_access_key => @secret_access_key,
89
- :use_ssl => @use_ssl,
90
- :timeout => @timeout,
91
- :debug => @debug,
92
- :proxy => @proxy)
97
+ @connection ||= Connection.new(:access_key_id => @access_key_id,
98
+ :secret_access_key => @secret_access_key,
99
+ :use_ssl => @use_ssl,
100
+ :timeout => @timeout,
101
+ :proxy => @proxy,
102
+ :debug => @debug)
93
103
  end
94
104
  end
105
+
95
106
  end
@@ -2,11 +2,11 @@ module Sndacs
2
2
 
3
3
  # Class responsible for generating signatures to requests.
4
4
  #
5
- # Implements algorithm defined by Amazon Web Services to sign
5
+ # Implements algorithm defined by GrandCloud Web Services to sign
6
6
  # request with secret private credentials
7
7
  #
8
8
  # === See
9
- # http://docs.amazonwebservices.com/AmazonS3/latest/index.html?RESTAuthentication.html
9
+ # https://cs-console.grandcloud.cn/public/docs/GrandCloud_Storage_Developer_Guide.pdf
10
10
 
11
11
  class Signature
12
12
 
@@ -62,34 +62,7 @@ module Sndacs
62
62
  CGI.escape(signature)
63
63
  end
64
64
 
65
- # Generates temporary URL for given resource
66
- #
67
- # ==== Options
68
- # * <tt>:bucket</tt> - Bucket in which the resource resides
69
- # * <tt>:resource</tt> - Path to the resouce you want to create
70
- # a temporary link to
71
- # * <tt>:access_key</tt> - Access key
72
- # * <tt>:secret_access_key</tt> - Secret access key
73
- # * <tt>:expires_at</tt> - Unix time stamp of when the resouce
74
- # link will expire
75
- # * <tt>:method</tt> - HTTP request method you want to use on
76
- # the resource, defaults to GET
77
- # * <tt>:headers</tt> - Any additional HTTP headers you intend
78
- # to use when requesting the resource
79
- def self.generate_temporary_url(options)
80
- bucket = options[:bucket]
81
- resource = options[:resource]
82
- access_key = options[:access_key]
83
- expires = options[:expires_at].to_i
84
- signature = generate_temporary_url_signature(options)
85
-
86
- url = "http://#{S3::HOST}/#{bucket}/#{resource}"
87
- url << "?AWSAccessKeyId=#{access_key}"
88
- url << "&Expires=#{expires}"
89
- url << "&Signature=#{signature}"
90
- end
91
-
92
- private
65
+ private
93
66
 
94
67
  def self.canonicalized_signature(options)
95
68
  headers = options[:headers] || {}
@@ -120,6 +93,7 @@ module Sndacs
120
93
  digest = OpenSSL::Digest::Digest.new("sha1")
121
94
  hmac = OpenSSL::HMAC.digest(digest, secret_access_key, string_to_sign)
122
95
  base64 = Base64.encode64(hmac)
96
+
123
97
  base64.chomp
124
98
  end
125
99
 
@@ -209,7 +183,7 @@ module Sndacs
209
183
  # requests that don't address a bucket, do nothing. For more
210
184
  # information on virtual hosted-style requests, see Virtual
211
185
  # Hosting of Buckets.
212
- bucket_name = host.sub(/\.?storage\.grandcloud\.cn\Z/, "")
186
+ bucket_name = host.sub(/\.?storage[a-zA-Z0-9\-]*?\.grandcloud\.cn\Z/, "")
213
187
  string << "/#{bucket_name}" unless bucket_name.empty?
214
188
 
215
189
  # 3. Append the path part of the un-decoded HTTP Request-URI,
@@ -240,4 +214,5 @@ module Sndacs
240
214
  string
241
215
  end
242
216
  end
217
+
243
218
  end
@@ -1,3 +1,3 @@
1
1
  module Sndacs
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/sndacs.rb CHANGED
@@ -1,3 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: utf-8 -*-
3
+
1
4
  require "base64"
2
5
  require "cgi"
3
6
  require "digest/md5"
@@ -13,6 +16,7 @@ require "sndacs/objects_extension"
13
16
  require "sndacs/buckets_extension"
14
17
  require "sndacs/parser"
15
18
  require "sndacs/bucket"
19
+ require "sndacs/config"
16
20
  require "sndacs/connection"
17
21
  require "sndacs/exceptions"
18
22
  require "sndacs/object"
@@ -22,6 +26,28 @@ require "sndacs/signature"
22
26
  require "sndacs/version"
23
27
 
24
28
  module Sndacs
25
- # Default (and only) host serving S3 stuff
26
- HOST = "storage.grandcloud.cn"
29
+
30
+ # Bucket default region
31
+ # NOTICE: DO NOT TOUCH THIS!!!
32
+ REGION_DEFAULT = 'huadong-1'
33
+
34
+ # Bucket region host template
35
+ # NOTICE: DO NOT TOUCH THIS!!!
36
+ REGION_HOST = 'storage-%s.grandcloud.cn'
37
+
38
+ # Object public access host template
39
+ # NOTICE: DO NOT TOUCH THIS!!!
40
+ REGION_CONTENT_HOST = 'storage-%s.sdcloud.cn'
41
+
42
+ # Default configurations, see Sndacs::Config for more info
43
+ #Config.access_key_id = ''
44
+ #Config.secret_access_key = ''
45
+ #Config.host = 'storage.grandcloud.cn'
46
+ #Config.content_host = 'storage.sdcloud.cn'
47
+ #Config.proxy = nil
48
+ #Config.timeout = 60
49
+ #Config.use_ssl = false
50
+ #Config.chunk_size = 1048576
51
+ #Config.debug = false
52
+
27
53
  end
data/sndacs.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
- # Load version requiring the canonical "s3/version", otherwise Ruby will think
4
- # is a different file and complaint about a double declaration of S3::VERSION.
3
+ # Load version requiring the canonical "sndacs/version", otherwise Ruby will think
4
+ # is a different file and complaint about a double declaration of Sndacs::VERSION.
5
5
  $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
6
6
  require "sndacs/version"
7
7
 
@@ -12,16 +12,16 @@ Gem::Specification.new do |s|
12
12
  s.authors = ["LI Daobing"]
13
13
  s.email = ["lidaobing@snda.com"]
14
14
  s.homepage = "https://github.com/grandcloud/sndacs-ruby"
15
- s.summary = "Library for accessing SNDA Cloud Storage objects and buckets"
15
+ s.summary = "Library for accessing SNDA Cloud Storage buckets and objects"
16
16
  s.description = "sndacs library provides access to SNDA Cloud Storage."
17
17
 
18
18
  s.required_rubygems_version = ">= 1.3.6"
19
19
 
20
20
  s.add_dependency "proxies", "~> 0.2.0"
21
- s.add_development_dependency "test-unit", ">= 2.0"
22
- s.add_development_dependency "mocha"
23
21
  s.add_development_dependency "bundler", ">= 1.0.0"
24
22
  s.add_development_dependency "rspec", "~> 2.0"
23
+ s.add_development_dependency "mocha"
24
+ #s.add_development_dependency "test-unit", ">= 2.0"
25
25
 
26
26
  s.files = `git ls-files`.split("\n")
27
27
  s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact