remote_files 1.0.0 → 1.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/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
|