remote_files 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/remote_files.rb +7 -0
- data/lib/remote_files/abstract_store.rb +26 -0
- data/lib/remote_files/file.rb +20 -0
- data/lib/remote_files/file_store.rb +47 -0
- data/lib/remote_files/fog_store.rb +11 -4
- data/lib/remote_files/memory_store.rb +46 -0
- data/lib/remote_files/mock_store.rb +3 -23
- data/lib/remote_files/version.rb +1 -1
- data/test/file_test.rb +9 -1
- data/test/fog_store_test.rb +61 -0
- data/test/remote_files_test.rb +9 -0
- metadata +6 -4
data/lib/remote_files.rb
CHANGED
@@ -63,6 +63,13 @@ module RemoteFiles
|
|
63
63
|
true
|
64
64
|
end
|
65
65
|
|
66
|
+
def self.delete!(file)
|
67
|
+
file.stored_in.each do |store_identifier|
|
68
|
+
store = lookup_store(store_identifier)
|
69
|
+
store.delete!(file.identifier)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
66
73
|
def self.synchronize!(file)
|
67
74
|
file.missing_stores.each do |store_identifier|
|
68
75
|
store = lookup_store(store_identifier)
|
@@ -6,6 +6,14 @@ module RemoteFiles
|
|
6
6
|
@identifier = identifier
|
7
7
|
end
|
8
8
|
|
9
|
+
def options
|
10
|
+
@options ||= {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def []=(name, value)
|
14
|
+
options[name] = value
|
15
|
+
end
|
16
|
+
|
9
17
|
def store!(file)
|
10
18
|
raise "You need to implement #{self.class.name}#store!"
|
11
19
|
end
|
@@ -14,8 +22,26 @@ module RemoteFiles
|
|
14
22
|
raise "You need to implement #{self.class.name}#retrieve!"
|
15
23
|
end
|
16
24
|
|
25
|
+
def delete!(identifier)
|
26
|
+
raise "You need to implement #{self.class.name}#delete!"
|
27
|
+
end
|
28
|
+
|
17
29
|
def url(identifier)
|
18
30
|
raise "You need to implement #{self.class.name}#url"
|
19
31
|
end
|
32
|
+
|
33
|
+
def url_matcher
|
34
|
+
raise "You need to implement #{self.class.name}:#url_matcher"
|
35
|
+
end
|
36
|
+
|
37
|
+
def file_from_url(url)
|
38
|
+
matched = url_matcher.match(url)
|
39
|
+
|
40
|
+
return nil unless matched
|
41
|
+
|
42
|
+
file_identifier = matched[1]
|
43
|
+
|
44
|
+
RemoteFiles::File.new(file_identifier, :stored_in => [identifier])
|
45
|
+
end
|
20
46
|
end
|
21
47
|
end
|
data/lib/remote_files/file.rb
CHANGED
@@ -10,6 +10,13 @@ module RemoteFiles
|
|
10
10
|
@options = options
|
11
11
|
end
|
12
12
|
|
13
|
+
def self.from_url(url)
|
14
|
+
RemoteFiles.stores.each do |store|
|
15
|
+
file = store.file_from_url(url)
|
16
|
+
return file if file
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
13
20
|
def options
|
14
21
|
@options.merge(
|
15
22
|
:identifier => identifier,
|
@@ -55,5 +62,18 @@ module RemoteFiles
|
|
55
62
|
def synchronize!
|
56
63
|
RemoteFiles.synchronize!(self)
|
57
64
|
end
|
65
|
+
|
66
|
+
def delete!
|
67
|
+
RemoteFiles.delete!(self)
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete
|
71
|
+
begin
|
72
|
+
delete!
|
73
|
+
true
|
74
|
+
rescue RemoteFiles::Error => e
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end
|
58
78
|
end
|
59
79
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
# This is good for use in deveopment
|
4
|
+
|
5
|
+
module RemoteFiles
|
6
|
+
class FileStore < AbstractStore
|
7
|
+
|
8
|
+
def directory
|
9
|
+
@directory ||= Pathname.new(options[:directory]).tap do |dir|
|
10
|
+
dir.mkdir unless dir.exist?
|
11
|
+
raise "#{dir} is not a directory" unless dir.directory?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def store!(file)
|
16
|
+
::File.open(directory + file.identifier, 'w') do |f|
|
17
|
+
f.write(file.content)
|
18
|
+
# what about content-type?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def retrieve!(identifier)
|
23
|
+
content = File.new(directory + identifier).read
|
24
|
+
|
25
|
+
::File.new(identifier,
|
26
|
+
:content => content,
|
27
|
+
:stored_in => [self.identifier]
|
28
|
+
# what about content-type? maybe use the mime-types gem?
|
29
|
+
)
|
30
|
+
rescue Errno::ENOENT => e
|
31
|
+
raise NotFoundError, e.message
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete!(identifier)
|
35
|
+
::File.delete(directory + identifier)
|
36
|
+
rescue Errno::ENOENT => e
|
37
|
+
end
|
38
|
+
|
39
|
+
def url(identifier)
|
40
|
+
"file://localhost/#{directory + identifier}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def url_matcher
|
44
|
+
@url_matcher ||= /file:\/\/localhost\/#{directory}\/(.*)/
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -43,12 +43,19 @@ module RemoteFiles
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
def
|
47
|
-
@
|
46
|
+
def url_matcher
|
47
|
+
@url_matcher ||= case options[:provider]
|
48
|
+
when 'AWS'
|
49
|
+
/https?:\/\/s3[^\.]*.amazonaws.com\/#{options[:directory]}\/(.*)/
|
50
|
+
when 'Rackspace'
|
51
|
+
/https?:\/\/storage.cloudfiles.com\/#{options[:directory]}\/(.*)/
|
52
|
+
else
|
53
|
+
raise "#{self.class.name}#url_matcher was not implemented for the #{options[:provider]} provider"
|
54
|
+
end
|
48
55
|
end
|
49
56
|
|
50
|
-
def
|
51
|
-
|
57
|
+
def delete!(identifier)
|
58
|
+
connection.delete_object(directory.key, identifier)
|
52
59
|
end
|
53
60
|
|
54
61
|
def connection
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# This is good for use in tests.
|
2
|
+
# Be sure to call #clear! before each test run.
|
3
|
+
|
4
|
+
module RemoteFiles
|
5
|
+
class MemoryStore < AbstractStore
|
6
|
+
def data
|
7
|
+
@data ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def clear!
|
11
|
+
data.clear
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.clear!
|
15
|
+
RemoteFiles.stores.each do |store|
|
16
|
+
store.clear! if store.is_a?(RemoteFiles::MemoryStore)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def store!(file)
|
21
|
+
data[file.identifier] = { :content => file.content, :content_type => file.content_type}
|
22
|
+
end
|
23
|
+
|
24
|
+
def retrieve!(identifier)
|
25
|
+
raise NotFoundError, "#{identifier} not found in #{self.identifier} store" unless data.has_key?(identifier)
|
26
|
+
|
27
|
+
File.new(identifier,
|
28
|
+
:content => data[identifier][:content],
|
29
|
+
:content_type => data[identifier][:content_type],
|
30
|
+
:stored_in => [self.identifier]
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete!(identifier)
|
35
|
+
data.delete(identifier)
|
36
|
+
end
|
37
|
+
|
38
|
+
def url(identifier)
|
39
|
+
"memory://#{self.identifier}/#{identifier}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def url_matcher
|
43
|
+
@url_matcher ||= /memory:\/\/#{identifier}\/(.*)/
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -1,25 +1,5 @@
|
|
1
|
-
|
2
|
-
class MockStore < AbstractStore
|
3
|
-
def data
|
4
|
-
@data ||= {}
|
5
|
-
end
|
6
|
-
|
7
|
-
def store!(file)
|
8
|
-
data[file.identifier] = { :content => file.content, :content_type => file.content_type}
|
9
|
-
end
|
10
|
-
|
11
|
-
def retrieve!(identifier)
|
12
|
-
raise NotFoundError, "#{identifier} not found in #{self.identifier} store" unless data.has_key?(identifier)
|
1
|
+
require 'remote_files/memory_store'
|
13
2
|
|
14
|
-
|
15
|
-
|
16
|
-
:content_type => data[identifier][:content_type],
|
17
|
-
:stored_in => [self.identifier]
|
18
|
-
)
|
19
|
-
end
|
20
|
-
|
21
|
-
def url(identifier)
|
22
|
-
"mock://#{self.identifier}/#{identifier}"
|
23
|
-
end
|
24
|
-
end
|
3
|
+
module RemoteFiles
|
4
|
+
MockStore = MemoryStore
|
25
5
|
end
|
data/lib/remote_files/version.rb
CHANGED
data/test/file_test.rb
CHANGED
@@ -63,7 +63,7 @@ describe RemoteFiles::File do
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
describe 'current_url' do
|
66
|
+
describe '#current_url' do
|
67
67
|
it 'should return the url from the first store where the file is currently stored' do
|
68
68
|
@s3.stubs(:url).returns('s3_url')
|
69
69
|
@cf.stubs(:url).returns('cf_url')
|
@@ -80,5 +80,13 @@ describe RemoteFiles::File do
|
|
80
80
|
@file.stored_in.replace([])
|
81
81
|
@file.current_url.must_be_nil
|
82
82
|
end
|
83
|
+
|
84
|
+
describe '::from_url' do
|
85
|
+
it 'should return a file from the first store that matches' do
|
86
|
+
url = 'http://something'
|
87
|
+
@cf.expects(:file_from_url).with(url).returns(@file)
|
88
|
+
assert_equal @file, RemoteFiles::File.from_url(url)
|
89
|
+
end
|
90
|
+
end
|
83
91
|
end
|
84
92
|
end
|
data/test/fog_store_test.rb
CHANGED
@@ -105,4 +105,65 @@ describe RemoteFiles::FogStore do
|
|
105
105
|
end
|
106
106
|
end
|
107
107
|
end
|
108
|
+
|
109
|
+
describe '#file_from_url' do
|
110
|
+
describe 'for an S3 store' do
|
111
|
+
before { @store[:provider] = 'AWS' }
|
112
|
+
|
113
|
+
it 'should create a file if the bucket matches' do
|
114
|
+
file = @store.file_from_url('http://s3-eu-west-1.amazonaws.com/directory/key/on/s3.txt')
|
115
|
+
assert file
|
116
|
+
assert_equal 'key/on/s3.txt', file.identifier
|
117
|
+
|
118
|
+
file = @store.file_from_url('http://s3-eu-west-1.amazonaws.com/other_bucket/key/on/s3.txt')
|
119
|
+
assert !file
|
120
|
+
|
121
|
+
file = @store.file_from_url('http://storage.cloudfiles.com/directory/key/on/s3.txt')
|
122
|
+
assert !file
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'for a cloudfiles store' do
|
127
|
+
before { @store[:provider] = 'Rackspace' }
|
128
|
+
|
129
|
+
it 'should create a file if the container matches' do
|
130
|
+
file = @store.file_from_url('http://storage.cloudfiles.com/directory/key/on/s3.txt')
|
131
|
+
assert file
|
132
|
+
assert_equal 'key/on/s3.txt', file.identifier
|
133
|
+
|
134
|
+
file = @store.file_from_url('http://storage.cloudfiles.com/other_container/key/on/s3.txt')
|
135
|
+
assert !file
|
136
|
+
|
137
|
+
file = @store.file_from_url('http://s3-eu-west-1.amazonaws.com/directory/key/on/s3.txt')
|
138
|
+
assert !file
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe 'for other stores' do
|
143
|
+
before { @store[:provider] = 'Google' }
|
144
|
+
|
145
|
+
it 'should raise a RuntimeError' do
|
146
|
+
proc { @store.file_from_url('http://s3-eu-west-1.amazonaws.com/directory/key/on/s3.txt') }.must_raise(RuntimeError)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe '#delete!' do
|
152
|
+
before do
|
153
|
+
@store.directory.files.create(
|
154
|
+
:body => 'content',
|
155
|
+
:content_type => 'text/plain',
|
156
|
+
:key => 'identifier',
|
157
|
+
)
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should destroy the file' do
|
161
|
+
assert @store.directory.files.get('identifier')
|
162
|
+
|
163
|
+
@store.delete!('identifier')
|
164
|
+
|
165
|
+
assert !@store.directory.files.get('identifier')
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
108
169
|
end
|
data/test/remote_files_test.rb
CHANGED
@@ -146,6 +146,15 @@ describe RemoteFiles do
|
|
146
146
|
end
|
147
147
|
end
|
148
148
|
|
149
|
+
describe '::delete!' do
|
150
|
+
it 'should delete the file from all the stores' do
|
151
|
+
@file.stored_in.replace([:mock1, :mock2])
|
152
|
+
@mock_store1.expects(:delete!).with(@file.identifier)
|
153
|
+
@mock_store2.expects(:delete!).with(@file.identifier)
|
154
|
+
RemoteFiles.delete!(@file)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
149
158
|
describe '::synchronize!' do
|
150
159
|
describe 'when the file is not stored anywhere' do
|
151
160
|
before { @file.stored_in.replace([]) }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: remote_files
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
12
|
+
date: 2012-09-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: fog
|
@@ -117,7 +117,9 @@ extra_rdoc_files: []
|
|
117
117
|
files:
|
118
118
|
- lib/remote_files/abstract_store.rb
|
119
119
|
- lib/remote_files/file.rb
|
120
|
+
- lib/remote_files/file_store.rb
|
120
121
|
- lib/remote_files/fog_store.rb
|
122
|
+
- lib/remote_files/memory_store.rb
|
121
123
|
- lib/remote_files/mock_store.rb
|
122
124
|
- lib/remote_files/resque_job.rb
|
123
125
|
- lib/remote_files/version.rb
|
@@ -142,7 +144,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
142
144
|
version: '0'
|
143
145
|
segments:
|
144
146
|
- 0
|
145
|
-
hash: -
|
147
|
+
hash: -3981899794566552461
|
146
148
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
149
|
none: false
|
148
150
|
requirements:
|
@@ -151,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
153
|
version: '0'
|
152
154
|
segments:
|
153
155
|
- 0
|
154
|
-
hash: -
|
156
|
+
hash: -3981899794566552461
|
155
157
|
requirements: []
|
156
158
|
rubyforge_project:
|
157
159
|
rubygems_version: 1.8.24
|