fastdfs-client 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 28e3d4bb5c9868a72bac8476c6d0580e124d852a
4
- data.tar.gz: 8889ef8b0df440d0ca99e3d1c213d9d2d6a7d9c1
3
+ metadata.gz: 83f837406bfbb7c59a4da740450c49232800e481
4
+ data.tar.gz: 900430e39aeea9db93faf42c460394eeb9087277
5
5
  SHA512:
6
- metadata.gz: 26e0eb1ebbf0d1d4934966f451e68db1e9bd280ab42e8c5ff1c822041f9e9b8ba5715e55e10cb145adacdffe79255b039b8bf3a1d1a2b939ee19712bb915100c
7
- data.tar.gz: 3a917eafa99d2dd236a99c28e3ab7dd851e3b20a64e2fd866253b1c39fa592f7ca87d273aaa91e7270bc3ebe0bbec19c129fb13fe9bd32a6067683d52a561f34
6
+ metadata.gz: 106f6c4cc8406215c7fe22d4935e4b72538f8e7f70874c1f66e129eac483669f8d2d1493bb09f15b2a2d2389fa8e34ffdc21ba5b3944361b53c35836838f303d
7
+ data.tar.gz: a12327a458920d7c1b4eccdf4828c1a7dc24d3ad0a7bd3d58d108b22fa7932d0cc050d1b0d3dc03b6a9bde3022dd33b349be9245383f18681f786ebc8ed06794
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fastdfs-client (0.0.2)
4
+ fastdfs-client (0.0.5)
5
5
 
6
6
  GEM
7
7
  remote: http://gems.ruby-china.org/
data/README.md CHANGED
@@ -10,12 +10,28 @@ fastdfs client for ruby
10
10
  ### Using
11
11
 
12
12
  ```RUBY
13
+
14
+ # return the result format
15
+ # {status: true, err_msg: "", result: ...}
16
+ #
17
+
18
+
13
19
  tracker = new Fastdfs::Client::Tracker("192.168.1.1", "22122")
14
20
 
15
21
  @storage = tracker.get_storage
16
22
 
17
23
  @storage.upload(@file)
24
+ #result: {group_name: "group1", path: "m1/xfsd/fds.jpg"}
25
+
26
+ @storage.delete(path, group_name)
27
+
28
+ # flag params [cover, merge]
29
+ @storage.set_metadata(path, group_name, {author: "kaka", width: "300"}, flag)
30
+
31
+ @storage.get_metadata(path, group_name)
32
+ #result: {author: "kaka", width: "300"}
33
+
34
+ @storage.download(path, group_name)
35
+ #result: #<Tempfile:/var/folders/m7/bt2j0rk54x555t44dpn4b7bm0000gn/T/test.jpg20160416-43738-1560vq3>
18
36
 
19
- #group_name + path
20
- @storage.delete(path)
21
37
  ```
@@ -6,6 +6,9 @@ module Fastdfs
6
6
  UPLOAD_FILE = 11
7
7
  RESP_CODE = 100
8
8
  DELETE_FILE = 12
9
+ GET_METADATA = 15
10
+ SET_METADATA = 13
11
+ DOWNLOAD_FILE = 14
9
12
  end
10
13
 
11
14
  end
@@ -11,8 +11,16 @@ module Fastdfs
11
11
  EXTNAME_LEN = 6
12
12
  GROUP_NAME_MAX_LEN = 16
13
13
 
14
+ RECV_MAX_LEN = 2 * 1024
15
+
16
+ RECORD_SEPERATOR = "\u0001"
17
+ FILE_SEPERATOR = "\u0002"
18
+
19
+ SET_METADATA_FLAG_OVERWRITE = "O"
20
+ SET_METADATA_FLAG_MERGE = "M"
21
+
14
22
  def self.header_bytes(cmd, hex_long, erron=0)
15
- hex_bytes = Utils.number_to_Buffer(hex_long)
23
+ hex_bytes = Utils.number_to_buffer(hex_long)
16
24
  header = hex_bytes.fill(0, hex_bytes.length...HEAD_LEN)
17
25
  header[8] = cmd
18
26
  header[9] = erron
@@ -10,13 +10,12 @@ module Fastdfs
10
10
  attr_accessor :header, :content, :header_len, :cmd, :socket, :host, :port
11
11
 
12
12
  def initialize(host, port, options = {})
13
- @host = host
14
- @port = port
15
- connection
13
+ @host, @port = host, port
16
14
  @header_len = ProtoCommon::HEAD_LEN
17
15
  @options = options || {}
18
16
  @connection_timeout = @options[:connection_timeout] || 3
19
- @recv_timeout = @options[:recv_timeout] || 3
17
+ @recv_timeout = @options[:recv_timeout] || 20
18
+ connection
20
19
  end
21
20
 
22
21
  def write(*args)
@@ -42,26 +41,54 @@ module Fastdfs
42
41
  !@socket.closed?
43
42
  end
44
43
 
45
- def receive
46
- @content = nil
47
- Timeout.timeout(@recv_timeout) do
44
+ def receive(&block)
45
+ timeout_recv do
48
46
  @header = @socket.recv(@header_len).unpack("C*")
49
47
  end
50
48
  res_header = parseHeader
51
- if res_header[:body_length] > 0
52
- Timeout.timeout(@recv_timeout) do
53
- @content = @socket.recv(@header.to_pack_long)
54
- end
49
+ if res_header[:status]
50
+ recv_body if is_recv?
51
+ res = yield(@content) if block_given?
52
+ res_header[:result] = res unless res.nil?
55
53
  end
56
- yield @content if block_given?
54
+ res_header
57
55
  end
58
56
 
59
57
  private
60
58
  def parseHeader
61
- raise "recv package size #{@header} != #{@header_len}, cmd: #{@cmd}" unless @header.length == @header_len
62
- raise "recv cmd: #{@header[8]} is not correct, expect cmd: #{CMD::RESP_CODE}, cmd: #{@cmd}" unless @header[8] == CMD::RESP_CODE
63
- raise "recv erron #{@header[9]} 0 is correct, cmd: #{@cmd}" unless @header[9] == 0
64
- {status: true, body_length: @header[0...8].to_pack_long}
59
+ err_msg = ""
60
+ err_msg = "recv package size #{@header} is not equal #{@header_len}, cmd: #{@cmd}" unless @header.length == @header_len
61
+ err_msg = "recv cmd: #{@header[8]} is not correct, expect cmd: #{CMD::RESP_CODE}, cmd: #{@cmd}" unless @header[8] == CMD::RESP_CODE
62
+ err_msg = "recv erron #{@header[9]}, 0 is correct cmd: #{@cmd}" unless @header[9] == 0
63
+ {status: err_msg.blank?, err_msg: err_msg}
64
+ end
65
+
66
+ def timeout_recv
67
+ Timeout.timeout(@recv_timeout) do
68
+ yield if block_given?
69
+ end
70
+ end
71
+
72
+ def is_recv?
73
+ recv_body_len > 0
74
+ end
75
+
76
+ def recv_body_len
77
+ @header[0...8].to_pack_long
78
+ end
79
+
80
+ def recv_body
81
+ @content = ""
82
+ max_len, body_len = ProtoCommon::RECV_MAX_LEN, recv_body_len
83
+
84
+ while body_len > 0
85
+ timeout_recv do
86
+ len = [body_len, max_len].min
87
+ @content << @socket.recv(len)
88
+ body_len -= len
89
+ end
90
+ end
91
+ @content = nil if @content.blank?
65
92
  end
66
93
 
67
94
  end
@@ -6,8 +6,8 @@ module Fastdfs
6
6
  class Storage
7
7
  extend Hook
8
8
 
9
- before(:upload, :delete){ @socket.connection }
10
- after(:upload, :delete){ @socket.close }
9
+ before(:upload, :delete, :get_metadata, ){ @socket.connection }
10
+ after(:upload, :delete, :get_metadata, ){ @socket.close }
11
11
 
12
12
  attr_accessor :host, :port, :group_name, :store_path, :socket, :options
13
13
 
@@ -28,26 +28,89 @@ module Fastdfs
28
28
 
29
29
  def delete(path, group_name = nil)
30
30
  cmd = CMD::DELETE_FILE
31
+ path_bytes = header_path_bytes(cmd, path, group_name)
32
+ @socket.write(cmd, path_bytes)
33
+ @socket.receive{ true }
34
+ end
35
+
36
+ def get_metadata(path, group_name = nil)
37
+ cmd = CMD::GET_METADATA
38
+ path_bytes = header_path_bytes(cmd, path, group_name)
39
+ @socket.write(cmd, path_bytes)
40
+ @socket.receive do |content|
41
+ res = content.split(ProtoCommon::RECORD_SEPERATOR).map do |c|
42
+ c.split(ProtoCommon::FILE_SEPERATOR)
43
+ end.flatten
44
+ Utils.symbolize_keys(Hash[*res])
45
+ end
46
+ end
47
+
48
+ def set_metadata(path, group_name = nil, options = {}, flag = :cover)
49
+ cmd = CMD::SET_METADATA
50
+
51
+ unless options.is_a?(Hash)
52
+ flag = options
53
+ options = {}
54
+ end
55
+
56
+ if group_name.is_a?(Hash)
57
+ options = group_name
58
+ group_name = nil
59
+ end
60
+ flag = convert_meta_flag(flag)
61
+ group_bytes, path_bytes = group_path_bytes(path, group_name)
62
+ meta_bytes = meta_to_bytes(options)
63
+
64
+ size_bytes = Utils.number_to_buffer(path_bytes.length) + Utils.number_to_buffer(meta_bytes.length)
65
+ size_bytes = (size_bytes).fill(0, size_bytes.length...16)
66
+ total = size_bytes.length + flag.length + group_bytes.length + path_bytes.length + meta_bytes.length
67
+ header_bytes = ProtoCommon.header_bytes(cmd, total)
68
+ @socket.write(cmd, (header_bytes + size_bytes + flag.bytes + group_bytes + path_bytes))
69
+ @socket.write(cmd, meta_bytes)
70
+ @socket.receive
71
+ end
72
+
73
+ def download(path, group_name = nil)
74
+ cmd = CMD::DOWNLOAD_FILE
75
+ group_bytes, path_bytes = group_path_bytes(path, group_name)
76
+ download_bytes = Utils.number_to_buffer(0) + Utils.number_to_buffer(0)
77
+
78
+ header_bytes = ProtoCommon.header_bytes(cmd, group_bytes.length + path_bytes.length + download_bytes.length)
79
+
80
+ @socket.write(cmd, header_bytes + download_bytes + group_bytes + path_bytes)
81
+ @socket.receive do |body|
82
+ create_tempfile(path, body) if body
83
+ end
84
+ end
85
+
86
+ private
87
+ def group_path_bytes(path, group_name = nil)
88
+ group_name, path = extract_path!(path, group_name)
89
+ group_bytes = group_name.bytes.fill(0, group_name.length...ProtoCommon::GROUP_NAME_MAX_LEN)
90
+ [group_bytes, path.bytes]
91
+ end
92
+
93
+ def header_path_bytes(cmd, path, group_name = nil)
94
+ path_bytes = group_path_bytes(path, group_name).flatten
95
+ return (ProtoCommon.header_bytes(cmd, path_bytes.length) + path_bytes)
96
+ end
97
+
98
+ def extract_path!(path, group_name = nil)
31
99
  raise "path arguments is empty!" if path.blank?
32
100
  if group_name.blank?
33
101
  group_name = /^\/?(\w+)/.match(path)[1]
34
102
  path = path.gsub(Regexp.new("/?#{group_name}/?"), "")
35
103
  end
36
104
  raise "group_name arguments is empty!" if group_name.blank?
37
- group_bytes = group_name.bytes.fill(0, group_name.length...ProtoCommon::GROUP_NAME_MAX_LEN)
38
- path_length = (group_bytes.length + path.bytes.length)
39
-
40
- @socket.write(cmd, (ProtoCommon.header_bytes(cmd, path_length) + group_bytes + path.bytes))
41
- @socket.receive{ true }
105
+ return group_name, path
42
106
  end
43
107
 
44
- private
45
108
  def _upload(file)
46
109
  cmd = CMD::UPLOAD_FILE
47
110
 
48
111
  extname = File.extname(file)[1..-1]
49
112
  ext_name_bs = extname.bytes.fill(0, extname.length...@extname_len)
50
- hex_len_bytes = Utils.number_to_Buffer(file.size)
113
+ hex_len_bytes = Utils.number_to_buffer(file.size)
51
114
  size_byte = [@store_path].concat(hex_len_bytes).fill(0, (hex_len_bytes.length+1)...@size_len)
52
115
 
53
116
  header = ProtoCommon.header_bytes(cmd, (size_byte.length + @extname_len + file.size))
@@ -58,12 +121,34 @@ module Fastdfs
58
121
  @socket.receive do |body|
59
122
  group_name_max_len = ProtoCommon::GROUP_NAME_MAX_LEN
60
123
 
61
- {
62
- group_name: body[0...group_name_max_len].strip,
63
- path: body[group_name_max_len..-1]
64
- }
124
+ {group_name: body[0...group_name_max_len].strip, path: body[group_name_max_len..-1]}
65
125
  end
66
126
  end
127
+
128
+ def convert_meta_flag(flag)
129
+ data = {
130
+ cover: ProtoCommon::SET_METADATA_FLAG_OVERWRITE,
131
+ merge: ProtoCommon::SET_METADATA_FLAG_MERGE
132
+ }
133
+ flag = :cover if flag.blank?
134
+ data[flag.to_sym]
135
+ end
136
+
137
+ def meta_to_bytes(options = {})
138
+ meta_bytes = options.map do |a|
139
+ a.join(ProtoCommon::FILE_SEPERATOR)
140
+ end.join(ProtoCommon::RECORD_SEPERATOR).bytes
141
+ meta_bytes << 0 if meta_bytes.blank?
142
+ meta_bytes
143
+ end
144
+
145
+ def create_tempfile(path, body)
146
+ tmp = Tempfile.new(path.gsub(/\//, "_"))
147
+ tmp.binmode
148
+ tmp.write(body)
149
+ tmp.close
150
+ tmp
151
+ end
67
152
 
68
153
  end
69
154
 
@@ -19,15 +19,14 @@ module Fastdfs
19
19
  def get_storage
20
20
  header = ProtoCommon.header_bytes(@cmd, 0)
21
21
  @socket.write(@cmd, header)
22
- @socket.receive do |body|
23
- storage_ip = body[ProtoCommon::IPADDR].strip
24
- storage_port = body[ProtoCommon::PORT].unpack("C*").to_pack_long
25
- store_path = body[ProtoCommon::TRACKER_BODY_LEN-1].unpack("C*")[0]
26
-
27
- Storage.new(storage_ip, storage_port, store_path, options)
28
- end
29
-
30
-
22
+ res = @socket.receive
23
+ return res unless res[:status]
24
+
25
+ storage_ip = @socket.content[ProtoCommon::IPADDR].strip
26
+ storage_port = @socket.content[ProtoCommon::PORT].unpack("C*").to_pack_long
27
+ store_path = @socket.content[ProtoCommon::TRACKER_BODY_LEN-1].unpack("C*")[0]
28
+
29
+ Storage.new(storage_ip, storage_port, store_path, options)
31
30
  end
32
31
  end
33
32
 
@@ -1,6 +1,6 @@
1
1
  module Utils
2
2
 
3
- def self.number_to_Buffer(num)
3
+ def self.number_to_buffer(num)
4
4
  8.times.map{|i| (num >> (56 - 8 * i)) & 255}
5
5
  end
6
6
 
@@ -10,4 +10,7 @@ module Utils
10
10
  arr1
11
11
  end
12
12
 
13
+ def self.symbolize_keys(obj)
14
+ obj.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
15
+ end
13
16
  end
@@ -1,5 +1,5 @@
1
1
  module Fastdfs
2
2
  module Client
3
- VERSION = '0.0.3'
3
+ VERSION = '0.0.5'
4
4
  end
5
5
  end
@@ -45,7 +45,7 @@ class TCPSocket
45
45
 
46
46
  group_name = Utils.array_merge([].fill(0, 0...16), TestConfig::GROUP_NAME.bytes)
47
47
  ip = Utils.array_merge([].fill(0, 0...15), TestConfig::STORAGE_IP.bytes)
48
- port = Utils.number_to_Buffer(TestConfig::STORAGE_PORT.to_i)
48
+ port = Utils.number_to_buffer(TestConfig::STORAGE_PORT.to_i)
49
49
  store_path = Array(TestConfig::STORE_PATH)
50
50
 
51
51
  (header+group_name+ip+port+store_path)[@recv_offset...@recv_offset+len].pack("C*")
@@ -68,6 +68,28 @@ class TCPSocket
68
68
  header = ProtoCommon.header_bytes(CMD::RESP_CODE, 0)
69
69
  header.pack("C*")
70
70
  end
71
+ },
72
+ "15" => {
73
+ recv_bytes: lambda do |len|
74
+ header = ProtoCommon.header_bytes(CMD::RESP_CODE, 0)
75
+ body = TestConfig::METADATA.map{|a| a.join(ProtoCommon::FILE_SEPERATOR)}.join(ProtoCommon::RECORD_SEPERATOR).bytes
76
+ header[7] = body.length
77
+ (header + body)[@recv_offset...@recv_offset+len].pack("C*")
78
+ end
79
+ },
80
+ "13" => {
81
+ recv_bytes: lambda do |len|
82
+ header = ProtoCommon.header_bytes(CMD::RESP_CODE, 0)
83
+ header.pack("C*")
84
+ end
85
+ },
86
+ "14" => {
87
+ recv_bytes: lambda do |len|
88
+ header = ProtoCommon.header_bytes(CMD::RESP_CODE, 0)
89
+ body = IO.read(TestConfig::FILE).bytes
90
+ header[7] = body.length
91
+ (header + body)[@recv_offset...@recv_offset+len].pack("C*")
92
+ end
71
93
  }
72
94
  }
73
95
  end
data/spec/storage_spec.rb CHANGED
@@ -8,33 +8,50 @@ describe Fastdfs::Client::Storage do
8
8
  let(:tracker){ FC::Tracker.new(host, port) }
9
9
  let(:storage){ tracker.get_storage }
10
10
 
11
- it "initialize the server" do
12
- expect(FC::Socket).to receive(:new).with(host, port, nil)
13
- FC::Storage.new(host, port)
14
- end
15
-
16
- it "should have access to the storage connection" do
17
- expect(storage.socket).to receive(:connection)
18
- expect(storage.socket).to receive(:close)
19
- storage.upload(TestConfig::FILE)
20
- end
21
-
22
- it "should the result attributes group_name and path" do
23
- res = storage.upload(TestConfig::FILE)
24
- expect(res).to include(:group_name)
25
- expect(res).to include(:path)
26
- end
27
-
28
- it "can delete file by group and path" do
29
- res = storage.upload(TestConfig::FILE)
30
- storage.delete(res[:path], res[:group_name])
31
- end
11
+ # it "initialize the server" do
12
+ # expect(FC::Socket).to receive(:new).with(host, port, nil)
13
+ # FC::Storage.new(host, port)
14
+ # end
15
+
16
+ # it "should have access to the storage connection" do
17
+ # expect(storage.socket).to receive(:connection)
18
+ # expect(storage.socket).to receive(:close)
19
+ # storage.upload(TestConfig::FILE)
20
+ # end
21
+
22
+ # it "should the result attributes group_name and path" do
23
+ # res = storage.upload(TestConfig::FILE)
24
+ # expect(res[:status]).to be_truthy
25
+ # expect(res[:result]).to include(:group_name)
26
+ # expect(res[:result]).to include(:path)
27
+ # end
28
+
29
+ # it "can delete file by group and path" do
30
+ # res = storage.upload(TestConfig::FILE)[:result]
31
+ # storage.delete(res[:path], res[:group_name])
32
+ # end
33
+
34
+ # it "can delete file raise exception" do
35
+ # res = storage.upload(TestConfig::FILE)[:result]
36
+ # result = FC::ProtoCommon.header_bytes(FC::CMD::RESP_CODE, 0, 22)
37
+ # TCPSocket.any_instance.stub("recv").and_return(result.pack("C*"))
38
+ # expect( storage.delete("fdsaf", res[:group_name])[:status] ).to be_falsey
39
+ # end
40
+
41
+ # it "can get metadata results" do
42
+ # res = storage.get_metadata("#{TestConfig::GROUP_NAME}/#{TestConfig::FILE_NAME}")
43
+ # expect(res[:result]).to eq(TestConfig::METADATA)
44
+ # end
45
+
46
+ # it "can set metadata" do
47
+ # expect(storage.set_metadata(TestConfig::FILE_NAME, TestConfig::GROUP_NAME, TestConfig::METADATA)).to be_truthy
48
+ # end
49
+
50
+ it "download the file to the local" do
51
+ res = storage.download("M00/04/46/wKgIF1b7XLWAI2Q6AAACVHeY6n8655.png", "group1")
52
+ expect(res[:status]).to be_truthy
53
+ expect(res[:result]).to be_an_instance_of(Tempfile)
54
+ expect(IO.read(res[:result])).to eq(IO.read(TestConfig::FILE))
32
55
 
33
- it "can delete file raise exception" do
34
- res = storage.upload(TestConfig::FILE)
35
- result = FC::ProtoCommon.header_bytes(FC::CMD::RESP_CODE, 0, 22)
36
- TCPSocket.any_instance.stub("recv").and_return(result.pack("C*"))
37
- expect{ storage.delete("fdsaf", res[:group_name]) }.to raise_error(RuntimeError)
38
56
  end
39
-
40
57
  end
data/spec/test_config.rb CHANGED
@@ -5,6 +5,13 @@ module TestConfig
5
5
  GROUP_NAME = "group1"
6
6
  FILE_NAME = "M00/04/47/wKgIF1cHcQyAeAF7AAACVHeY6n8267.png"
7
7
 
8
+ METADATA = {
9
+ width: "800",
10
+ height: "600",
11
+ bgcolor: 'red',
12
+ author: "kaka"
13
+ }
14
+
8
15
  FILE = Tempfile.new("test.jpg")
9
16
  FILE.write("testtest")
10
17
  FILE.close
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastdfs-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ka Ka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-12 00:00:00.000000000 Z
11
+ date: 2016-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -31,6 +31,7 @@ executables: []
31
31
  extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
+ - ".gitignore"
34
35
  - Gemfile
35
36
  - Gemfile.lock
36
37
  - README.md