fastdfs-client 0.0.3 → 0.0.5

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.
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