sndacs 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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