uricp 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +16 -0
  4. data/README.md +31 -0
  5. data/README.rdoc +23 -0
  6. data/Rakefile +66 -0
  7. data/bin/segment_upload +66 -0
  8. data/bin/uricp +103 -0
  9. data/features/auth_download.feature +106 -0
  10. data/features/cacheable_from_uri.feature +71 -0
  11. data/features/documented_options.feature +73 -0
  12. data/features/remote_upload.feature +49 -0
  13. data/features/segmented_upload.feature +27 -0
  14. data/features/segmented_upload_options.feature +65 -0
  15. data/features/step_definitions/orbit_steps.rb +194 -0
  16. data/features/step_definitions/uricp_steps.rb +34 -0
  17. data/features/support/env.rb +20 -0
  18. data/features/userid_download.feature +21 -0
  19. data/lib/segment_upload.rb +38 -0
  20. data/lib/uricp/curl_primitives.rb +45 -0
  21. data/lib/uricp/orbit_auth.rb +59 -0
  22. data/lib/uricp/segmenter.rb +88 -0
  23. data/lib/uricp/strategy/cache_common.rb +48 -0
  24. data/lib/uricp/strategy/cached_get.rb +48 -0
  25. data/lib/uricp/strategy/cleaner.rb +28 -0
  26. data/lib/uricp/strategy/common.rb +108 -0
  27. data/lib/uricp/strategy/local_convert.rb +36 -0
  28. data/lib/uricp/strategy/local_convert_common.rb +29 -0
  29. data/lib/uricp/strategy/local_link.rb +31 -0
  30. data/lib/uricp/strategy/piped_cache.rb +40 -0
  31. data/lib/uricp/strategy/piped_cache_convert.rb +45 -0
  32. data/lib/uricp/strategy/piped_compress.rb +30 -0
  33. data/lib/uricp/strategy/piped_decompress.rb +35 -0
  34. data/lib/uricp/strategy/piped_local_compress.rb +30 -0
  35. data/lib/uricp/strategy/piped_local_get.rb +29 -0
  36. data/lib/uricp/strategy/piped_local_put.rb +29 -0
  37. data/lib/uricp/strategy/piped_remote_get.rb +38 -0
  38. data/lib/uricp/strategy/pruner.rb +22 -0
  39. data/lib/uricp/strategy/remote_put.rb +47 -0
  40. data/lib/uricp/strategy/segmented_remote_put.rb +52 -0
  41. data/lib/uricp/strategy/sweeper.rb +28 -0
  42. data/lib/uricp/uri_strategy.rb +47 -0
  43. data/lib/uricp/version.rb +4 -0
  44. data/lib/uricp.rb +56 -0
  45. data/spec/something_spec.rb +5 -0
  46. data/uricp.gemspec +32 -0
  47. metadata +281 -0
@@ -0,0 +1,65 @@
1
+ Feature: Documented Help for Segmented Upload
2
+ In order to work out what segment_upload does
3
+ As a command line user
4
+ I want to get detailed usage information
5
+
6
+ Scenario: Basic UI
7
+ When I get help for "segment_upload"
8
+ Then the exit status should be 0
9
+ And the banner should include the version
10
+ And the banner should document that this app takes options
11
+ And the following options should be documented:
12
+ |--version|
13
+ |--auth-token|
14
+ |--auth-user|
15
+ |--auth-key|
16
+ |--from|
17
+ |--segment-size|
18
+ And the option "dry-run" should be documented which is negatable
19
+ And the banner should document that this app's arguments are:
20
+ |to_uri|which is required|
21
+
22
+ Scenario: auth token and auth-user are mutually exclusive
23
+ When I run `segment_upload --auth-token abcdef --auth-user xyz --auth-key abc http://source file:///target`
24
+ Then the exit status should not be 0
25
+ And the stderr should contain "needless argument"
26
+
27
+ Scenario: auth user needs auth key
28
+ When I run `segment_upload --auth-user xyz http://source`
29
+ Then the exit status should not be 0
30
+ And the stderr should contain "missing argument"
31
+
32
+ Scenario: auth key needs auth user
33
+ When I run `segment_upload --auth-user xyz http://source`
34
+ Then the exit status should not be 0
35
+ And the stderr should contain "missing argument"
36
+
37
+ Scenario: needs some sort of authentication
38
+ When I run `segment_upload http://source`
39
+ Then the exit status should not be 0
40
+ And the stderr should contain "missing argument"
41
+
42
+ Scenario: no http should fail
43
+ When I run `segment_upload ftp://source`
44
+ Then the exit status should not be 0
45
+ And the stderr should contain "unsupported url"
46
+
47
+ Scenario: authentication with normal http should fail
48
+ When I run `segment_upload --auth-user cli-xxxxx --auth-key fred https://foss-lab-manual.googlecode.com/files`
49
+ Then the exit status should not be 0
50
+ And the stderr should contain "Cannot authenticate"
51
+
52
+ Scenario: bad authentication should fail against orbit
53
+ When I run `segment_upload --auth-user cli-xxxxx --auth-key fred https://orbit.brightbox.com/v1/acc-xxxxx/test/test.img`
54
+ Then the exit status should not be 0
55
+ And the stderr should contain "Cannot authenticate"
56
+
57
+ Scenario: should not accept plain number for segment size
58
+ When I run `segment_upload --segment-size 100 --auth-token abcdef https:///orbit.brightbox.com/v1/acc-xxxxx/test/test.img`
59
+ Then the exit status should not be 0
60
+ And the stderr should contain "Unparseable filesize"
61
+
62
+ Scenario: should trap attempts to open a command
63
+ When I run `segment_upload --segment-size 100MB --auth-token abcdef --from ' |fred' https:///orbit.brightbox.com/v1/acc-xxxxx/test/test.img`
64
+ Then the exit status should not be 0
65
+ And the stderr should contain "invalid argument"
@@ -0,0 +1,194 @@
1
+ require 'inifile'
2
+
3
+ def obtain_credentials
4
+ config = IniFile.load(ENV['HOME']+ '/.brightbox/config') ||
5
+ raise(RuntimeError, "No brightbox config in home directory. Can't obtain auth token for tests")
6
+ section = config.sections.first
7
+ [config[section]['client_id'], config[section]['secret']]
8
+ end
9
+
10
+ def fetch_orbit_token
11
+ @clientid, @key = obtain_credentials
12
+ cmd = "curl -I https://orbit.brightbox.com/v1 -H 'X-Auth-User: #{@clientid}' -H 'X-Auth-Key: #{@key}'"
13
+ run_simple(unescape(cmd))
14
+ stdout_from(cmd).each_line do |line|
15
+ key, value = line.strip.split(/\s*:\s*/,2)
16
+ @current_auth_token = value if key == 'X-Auth-Token'
17
+ @current_storage_url = value if key == 'X-Storage-Url'
18
+ end
19
+ end
20
+
21
+ def create_download_file(filetype, name, container)
22
+ upload_url = File.join(@current_storage_url, container, name)
23
+ tempfile = '/tmp/fred55322'
24
+ FileUtils.rm_f(Dir.glob(tempfile+'*'))
25
+ format = 'raw'
26
+ format = 'qcow2' if filetype == 'qcow2'
27
+ cmd = "qemu-img create -q -f #{format} #{tempfile} 1G"
28
+ system(unescape(cmd))
29
+ cmd = "cat #{tempfile}"
30
+ if filetype == 'lz4'
31
+ cmd += " | lz4c "
32
+ end
33
+ cmd += "| curl --silent --fail -T - -H 'X-Auth-Token: #{@current_auth_token}' #{upload_url} "
34
+ system(unescape(cmd))
35
+ end
36
+
37
+ def create_container(name)
38
+ url = File.join(@current_storage_url, name)
39
+ cmd = "curl --silent --fail -I -H 'X-Auth-Token: #{@current_auth_token}' #{url}>/dev/null || curl --silent --fail -I -H 'X-Auth-Token: #{@current_auth_token}' #{url} -X PUT"
40
+ system(unescape(cmd))
41
+ end
42
+
43
+ Given(/^a container called "([^"]*)"$/) do |container|
44
+ create_container(container)
45
+ end
46
+
47
+ Given(/^a cache of "(.*?)" from container "(.*?)" at "(.*?)"$/) do |name, container, cache|
48
+ url = File.join(@current_storage_url, container, name)
49
+ target = File.join(cache, 'cache', name)
50
+ steps %{
51
+ When I successfully run `curl --fail --silent -o #{target} -H 'X-Auth-Token: #{@current_auth_token}' #{url}`
52
+ }
53
+ end
54
+
55
+ Given /^a (\d+) byte lz4 file named "([^"]*)"$/ do |file_size, file_name|
56
+ write_fixed_size_file(file_name, file_size.to_i)
57
+ system("lz4c #{file_name}")
58
+ File.rename file_name+'.lz4', file_name
59
+ end
60
+
61
+ When(/^I store "([^"]*)" into container "([^"]*)" from "([^"]*)"$/) do |name, container, source|
62
+ steps %{
63
+ When I store "#{name}" with options "" into container "#{container}" from "#{source}"
64
+ }
65
+ end
66
+
67
+ When(/^I retrieve "([^"]*)" with options "([^"]*)" from container "([^"]*)" into "([^"]*)"$/) do |name, runoptions, container, target|
68
+ url = File.join(@current_storage_url, container, name)
69
+ steps %{
70
+ When I successfully run `uricp #{runoptions} --auth-token #{@current_auth_token} #{url} #{target}`
71
+ }
72
+ end
73
+
74
+ When(/^I retrieve "([^"]*)" from container "([^"]*)" into "([^"]*)" with a userid$/) do |name, container, target|
75
+ url = File.join(@current_storage_url, container, name)
76
+ steps %{
77
+ When I successfully run `uricp --auth-user '#{@clientid}' --auth-key '#{@key}' #{url} #{target}`
78
+ }
79
+ end
80
+
81
+ When(/^I store "([^"]*)" into container "([^"]*)" from "([^"]*)" with a userid$/) do |name, container, source|
82
+ @current_container = container
83
+ @current_name = name
84
+ url = File.join(@current_storage_url, container, name)
85
+ steps %{
86
+ When I successfully run `uricp --auth-user '#{@clientid}' --auth-key '#{@key}' #{source} #{url}`
87
+ }
88
+ end
89
+
90
+ When(/^I retrieve "([^"]*)" from container "([^"]*)" into "([^"]*)"$/) do |name, container, target|
91
+ steps %{
92
+ When I retrieve "#{name}" with options "" from container "#{container}" into "#{target}"
93
+ }
94
+ end
95
+
96
+ When(/^I store "([^"]*)" with options "([^"]*)" into container "([^"]*)" from "([^"]*)"$/) do |name, runoptions, container, source|
97
+ @current_container = container
98
+ @current_name = name
99
+ url = File.join(@current_storage_url, container, name)
100
+ steps %{
101
+ When I successfully run `uricp #{runoptions} --auth-token #{@current_auth_token} #{source} #{url}`
102
+ }
103
+ end
104
+
105
+ When(/^I store "([^"]*)" with segment size "([^"]*)" into container "([^"]*)" from "([^"]*)" with a userid$/) do |name, segment_size, container, source|
106
+ @current_container = container
107
+ @current_name = name
108
+ url = File.join(@current_storage_url, container, name)
109
+ steps %{
110
+ When I successfully run `uricp --auth-user '#{@clientid}' --auth-key '#{@key}' --segment-size '#{segment_size}' #{source} #{url}`
111
+ }
112
+ end
113
+
114
+ When(/^I store "([^"]*)" with segment size "([^"]*)" and options "([^"]*)" into container "([^"]*)" from "([^"]*)" with a userid$/) do |name, segment_size, runoptions, container, source|
115
+ @current_container = container
116
+ @current_name = name
117
+ url = File.join(@current_storage_url, container, name)
118
+ steps %{
119
+ When I successfully run `uricp #{runoptions} --auth-user '#{@clientid}' --auth-key '#{@key}' --segment-size '#{segment_size}' #{source} #{url}`
120
+ }
121
+ end
122
+
123
+ When(/^I upload "([^"]*)" with segment size "([^"]*)" into container "([^"]*)" from "([^"]*)"$/) do |name, segment_size, container, source|
124
+ @current_container = container
125
+ @current_name = name
126
+ url = File.join(@current_storage_url, container, name)
127
+ steps %{
128
+ When I successfully run `segment_upload --from #{source} --auth-token #{@current_auth_token} --segment-size #{segment_size} #{url}`
129
+ }
130
+ end
131
+
132
+ When(/^I upload "([^"]*)" with segment size "([^"]*)" into container "([^"]*)" from "([^"]*)" as a stream$/) do |name, segment_size, container, source|
133
+ @current_container = container
134
+ @current_name = name
135
+ url = File.join(@current_storage_url, container, name)
136
+ steps %{
137
+ When I successfully run `sh -c 'cat #{source} | segment_upload --auth-token #{@current_auth_token} --segment-size #{segment_size} #{url}'`
138
+ }
139
+ end
140
+
141
+ Then(/^a (\d+) byte entry should exist in container "(.*?)" called "(.*?)"$/) do |size, container, name|
142
+ url = File.join(@current_storage_url, container, name)
143
+ command = "curl -I --fail --silent -H 'X-Auth-Token:#{@current_auth_token}' #{url}"
144
+ steps %{
145
+ When I successfully run `#{command}`
146
+ Then the output should match /\\sContent-Length: #{size}\\s/
147
+ }
148
+ end
149
+
150
+ Then(/^the container "(.*?)" should contain (\d+) entries$/) do |container, quantity|
151
+ url = File.join(@current_storage_url, container)
152
+ command = "curl -I --fail --silent -H 'X-Auth-Token:#{@current_auth_token}' #{url}"
153
+ steps %{
154
+ When I successfully run `#{command}`
155
+ Then the output should match /\\sX-Container-Object-Count: #{quantity}\\s/
156
+ }
157
+ end
158
+
159
+ Then(/^an lz4 compressed entry should exist in container "(.*?)" called "(.*?)"$/) do |container, name|
160
+ url = File.join(@current_storage_url, container, name)
161
+ cmd="curl -r 0-3 --fail --silent -H X-Auth-Token:#{@current_auth_token} #{url}"
162
+ run_simple(unescape(cmd))
163
+ assert_exact_output([0x184D2204].pack('V'), stdout_from(cmd))
164
+ end
165
+
166
+ Then(/^a qcow2 entry should exist in container "(.*?)" called "(.*?)"$/) do |container, name|
167
+ url = File.join(@current_storage_url, container, name)
168
+ cmd="curl -r 0-3 --fail --silent -H X-Auth-Token:#{@current_auth_token} #{url}"
169
+ run_simple(unescape(cmd))
170
+ assert_exact_output(['QFI',0xfb].pack('a3C'), stdout_from(cmd))
171
+ end
172
+
173
+ Before('@orbit') do
174
+ fetch_orbit_token
175
+ end
176
+
177
+ Before('@orbitdownloads') do
178
+ $orbit_setup ||= false
179
+ unless $orbit_setup
180
+ create_container('test')
181
+ create_download_file('qcow2', 'img-qcow2', 'test')
182
+ create_download_file('lz4', 'img-lz4cy', 'test')
183
+ $orbit_setup = true
184
+ end
185
+ end
186
+
187
+ After('@orbit') do
188
+ if @current_auth_token && @current_container
189
+ steps %{
190
+ When I successfully run `swift --os-storage-url=#{@current_storage_url} --os-auth-token=#{@current_auth_token} delete #{@current_container} #{@current_name}`
191
+ }
192
+ @current_container = @current_name = nil
193
+ end
194
+ end
@@ -0,0 +1,34 @@
1
+ Then(/^the file named "(.*?)" should have a file format of "(.*?)"$/) do |filename, format|
2
+ case format
3
+ when 'lz4'
4
+ assert_exact_output([0x184D2204].pack('V'),
5
+ File.open(filename, 'rb') {|f| f.read(4) })
6
+ when 'qcow2v3', 'qcow3'
7
+ steps %{
8
+ When I successfully run `qemu-img info #{filename}`
9
+ Then the output from "qemu-img info #{filename}" should contain "file format: qcow2"
10
+ And the output from "qemu-img info #{filename}" should contain "compat: 1.1"
11
+ }
12
+ else
13
+ assert_no_partial_output([0x184D2204].pack('V'),
14
+ File.open(filename, 'rb') {|f| f.read(4) })
15
+ steps(%{
16
+ When I successfully run `qemu-img info #{filename}`
17
+ Then the output from "qemu-img info #{filename}" should contain "file format: #{format}"
18
+ And the output from "qemu-img info #{filename}" should not contain "compat: 1.1"
19
+ })
20
+ end
21
+ end
22
+
23
+ Given(/^a correctly initialised cache at "(.*?)"$/) do |basedir|
24
+ steps %{
25
+ Given a directory named "#{basedir}/cache"
26
+ And a directory named "#{basedir}/temp"
27
+ }
28
+ end
29
+
30
+ Given(/^an empty directory named "([^"]*)"$/) do |dir_name|
31
+ create_dir(dir_name)
32
+ FileUtils.rm_rf(File.join(dir_name, '.'), :secure => true)
33
+ end
34
+
@@ -0,0 +1,20 @@
1
+ require 'aruba/cucumber'
2
+ require 'methadone/cucumber'
3
+ require 'fileutils'
4
+
5
+ ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
6
+ LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
7
+
8
+ Before do
9
+ # Using "announce" causes massive warnings on 1.9.2
10
+ @puts = true
11
+ @original_rubylib = ENV['RUBYLIB']
12
+ ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
13
+ end
14
+
15
+ After do
16
+ ENV['RUBYLIB'] = @original_rubylib
17
+
18
+ FileUtils.rm_rf(Dir.glob('/tmp/uricp/*'))
19
+
20
+ end
@@ -0,0 +1,21 @@
1
+ @orbit
2
+ @orbitdownloads
3
+ Feature: User authenticated download of images from orbit
4
+ In order to download images from orbit
5
+ As a command line user
6
+ I want to retrieve the URI via an optional cache and copy correctly to target in the right format using a user id directly
7
+
8
+ Background:
9
+ Given an empty directory named "/tmp/uricp"
10
+ And the default aruba timeout is 15 seconds
11
+ And a container called "test"
12
+
13
+ Scenario: qcow download no conversion, no cache with userid
14
+ When I retrieve "img-qcow2" from container "test" into "file:///tmp/uricp/srv-test1" with a userid
15
+ Then a file named "/tmp/uricp/srv-test1" should exist
16
+ And the file named "/tmp/uricp/srv-test1" should have a file format of "qcow2v3"
17
+
18
+ Scenario: direct upload with userid
19
+ Given a 102400 byte file named "/tmp/uricp/srv-testy"
20
+ When I store "img-usrid" into container "test" from "file:///tmp/uricp/srv-testy" with a userid
21
+ Then a 102400 byte entry should exist in container "test" called "img-usrid"
@@ -0,0 +1,38 @@
1
+ require "uricp/version"
2
+ require "uricp/curl_primitives"
3
+ require "uricp/orbit_auth"
4
+ require "uricp/segmenter"
5
+
6
+ module Uricp
7
+
8
+ UnsupportedURLtype = Class.new(ArgumentError)
9
+
10
+ end
11
+
12
+ #Monkey patch a copy_stream facility in using 'sendfile'
13
+ unless IO.respond_to? :copy_stream
14
+ require 'sendfile'
15
+
16
+ def IO.copy_stream(src, dst, copy_length=nil, offset=nil)
17
+ unless src.stat.pipe?
18
+ current_pos = src.pos
19
+ count = dst.sendfile(src, offset || current_pos, copy_length)
20
+ src.seek(count, IO::SEEK_CUR)
21
+ return count
22
+ else
23
+ amount = copy_length.to_i
24
+ buf_size = 2**16
25
+ buffer=""
26
+ while amount > 0 do
27
+ src.read(buf_size, buffer)
28
+ amount_read = buffer.length
29
+ dst.write(buffer)
30
+ amount -= amount_read
31
+ break if src.eof?
32
+ end
33
+ return copy_length.to_i - amount
34
+ end
35
+ rescue EOFError
36
+ 0
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ module Uricp::CurlPrimitives
2
+
3
+ attr_reader :options
4
+
5
+ def from
6
+ options['from_uri']
7
+ end
8
+
9
+ def from=(target)
10
+ options['from_uri'] = target
11
+ end
12
+
13
+ def to
14
+ options['to_uri']
15
+ end
16
+
17
+ def to=(target)
18
+ options['to_uri'] = target
19
+ end
20
+
21
+ def curl_command
22
+ "curl --fail --silent"
23
+ end
24
+
25
+ def authentication
26
+ "-H X-Auth-Token:#{options['auth-token']}" if http_authentication?
27
+ end
28
+
29
+ def http_authentication?
30
+ options['auth-token']
31
+ end
32
+
33
+ def curl_upload_from(source, destination = to)
34
+ "#{curl_command} #{authentication} -T #{source} #{destination.to_s};"
35
+ end
36
+
37
+ def curl_download_to_pipe
38
+ "#{curl_command} #{authentication} #{from.to_s} |"
39
+ end
40
+
41
+ def curl_manifest(object_manifest, destination = to)
42
+ "#{curl_command} #{authentication} -X PUT -H 'X-Object-Manifest: #{object_manifest}' #{destination.to_s} --data-binary ''"
43
+ end
44
+
45
+ end
@@ -0,0 +1,59 @@
1
+ require 'open-uri'
2
+ module Uricp
3
+
4
+ class OrbitAuth
5
+
6
+ AuthenticationFailure = Class.new(ArgumentError)
7
+
8
+ def initialize(auth_uri, auth_id, auth_key)
9
+ auth_uri.open(
10
+ 'X-Auth-User' => auth_id,
11
+ 'X-Auth-Key' => auth_key,
12
+ 'Range' => 'bytes=0-0'
13
+ ) do |uri|
14
+ @storage_url = uri.meta['x-storage-url']
15
+ @token = uri.meta['x-auth-token']
16
+ end
17
+ rescue OpenURI::HTTPError => e
18
+ raise AuthenticationFailure, "Cannot authenticate against #{auth_uri}"
19
+ end
20
+
21
+ attr_reader :storage_url, :token
22
+
23
+ def self.validate_options(options)
24
+ if options['auth-token'] && (options['auth-key'] || options['auth-user'])
25
+ raise ::OptionParser::NeedlessArgument,
26
+ "use either key based or token based authentication"
27
+ end
28
+ if options['auth-key'].nil? ^ options['auth-user'].nil?
29
+ raise ::OptionParser::MissingArgument,
30
+ "'auth-user' requires 'auth-key'"
31
+ end
32
+ if (options['auth-token'] || options['auth-user']) && options['auth_uri'].nil?
33
+ raise ::OptionParser::NeedlessArgument,
34
+ "authentication is for http uris only"
35
+ end
36
+ end
37
+
38
+ def self.add_auth_token(options)
39
+ if options['auth-user']
40
+ orbit_credentials = self.new(options['auth_uri'],
41
+ options['auth-user'], options['auth-key'])
42
+ options['auth-token'] = orbit_credentials.token
43
+ options.delete('auth-key')
44
+ options.delete('auth-user')
45
+ end
46
+ end
47
+
48
+ def self.add_auth_to_optionparser(app)
49
+ app.on("--auth-token AUTH_TOKEN",
50
+ "Use AUTH_TOKEN for non-local requests")
51
+ app.on("--auth-user AUTH_USER",
52
+ "Use AUTH_USER for authentication")
53
+ app.on("--auth-key AUTH_KEY",
54
+ "Use AUTH_KEY for authentication")
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,88 @@
1
+ require 'pathname'
2
+
3
+ module Uricp
4
+
5
+ class Segmenter
6
+
7
+ include Methadone::CLILogging
8
+ include Methadone::SH
9
+ include Uricp::CurlPrimitives
10
+
11
+ def initialize(options)
12
+ @options = options
13
+ source = options['from']
14
+ if @source = source && open(source)
15
+ @stream = File.pipe?(source) || File.chardev?(source)
16
+ else
17
+ @source = STDIN
18
+ @stream = true
19
+ end
20
+ split_path
21
+ end
22
+
23
+ def split_path
24
+ elements = Pathname.new(to.path).enum_for(:each_filename).to_a
25
+ elements.shift
26
+ @account=elements.shift
27
+ @container=elements.shift
28
+ @object_path=File.join(elements)
29
+ end
30
+
31
+ def upload
32
+ if large_upload?
33
+ segmented_upload
34
+ else
35
+ sh! curl_upload_from(options['from']),
36
+ :on_fail => "Upload to #{to} failed"
37
+ end
38
+ end
39
+
40
+ def segment_size
41
+ options['segment-size'].to_i
42
+ end
43
+
44
+ def stream?
45
+ @stream
46
+ end
47
+
48
+ def large_upload?
49
+ stream? || @source.stat.size > segment_size
50
+ end
51
+
52
+ def manifest_suffix
53
+ @manifest_suffix ||= [Time.now.to_f, @source.stat.size, segment_size].join('/')
54
+ end
55
+
56
+ def object_manifest
57
+ @object_manifest ||= File.join(@container, @object_path, manifest_suffix)+'/'
58
+ end
59
+
60
+ def segmented_upload
61
+ debug "#{self.class.name}: Large upload detected - segmenting into #{segment_size} byte chunks."
62
+ suffix = 0
63
+ until @source.eof?
64
+ debug "#{self.class.name}: Uploading segment #{suffix}"
65
+ upload_segment(suffix)
66
+ suffix = suffix.next
67
+ end
68
+ add_manifest
69
+ end
70
+
71
+ def upload_segment(segment_number)
72
+ segment_name = File.join(to.to_s, manifest_suffix, '%08d' % segment_number)
73
+ debug "Uploading with #{curl_upload_from('-', segment_name)}"
74
+ open('|'+curl_upload_from('-', segment_name), 'w') do |destination|
75
+ copy_length = IO.copy_stream(@source, destination, segment_size)
76
+ debug "#{self.class.name}: Uploaded #{copy_length} bytes to #{segment_name}"
77
+ end
78
+ end
79
+
80
+ def add_manifest
81
+ debug "Adding DLO object_manifest #{object_manifest}"
82
+ sh! curl_manifest(object_manifest),
83
+ :on_fail => "Upload to #{to} failed"
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,48 @@
1
+ require 'fileutils'
2
+
3
+ module Uricp::Strategy
4
+
5
+ module CacheCommon
6
+
7
+ def validate_cache!
8
+ raise Uricp::MissingCache,
9
+ "No cache found at #{cache_root}. Expected a 'cache' and 'temp' directory" unless cache_exists?
10
+ raise Uricp::MissingCache,
11
+ "No cache name found" unless cache_name
12
+ end
13
+
14
+ def in_cache?
15
+ File.readable? cache_file
16
+ end
17
+
18
+ def cache_root
19
+ options['cache']
20
+ end
21
+
22
+ def cache_name
23
+ options['cache_name']
24
+ end
25
+
26
+ def cache_exists?
27
+ cache_root && %w{temp cache}.all? do |d|
28
+ File.directory?(File.join(cache_root, d))
29
+ end
30
+ end
31
+
32
+ def temp_cache_file
33
+ @temp_cache_file ||= File.join(cache_root, 'temp', cache_name)
34
+ end
35
+
36
+ def temp_cache_uri
37
+ URI.join('file:///', temp_cache_file)
38
+ end
39
+
40
+ def cache_file
41
+ @cache_file ||= File.join(cache_root, 'cache', cache_name)
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+
@@ -0,0 +1,48 @@
1
+ require 'uri'
2
+
3
+ module Uricp::Strategy
4
+
5
+ class CachedGet
6
+
7
+ include Uricp::Strategy::Common
8
+ include Uricp::Strategy::CacheCommon
9
+
10
+ def appropriate?
11
+ if cache_root
12
+ validate_cache!
13
+ if in_cache? || file_source?
14
+ return proposal
15
+ else
16
+ debug "#{self.class.name}: no cache entry for #{options['from_uri']}"
17
+ end
18
+ else
19
+ debug "#{self.class.name}: not appropriate"
20
+ end
21
+ false
22
+ end
23
+
24
+ def command
25
+ ":;"
26
+ end
27
+
28
+ def proposal
29
+ @proposed_options = options.dup
30
+ unless file_source?
31
+ @proposed_options['from_uri'] = URI.join('file:///', cache_file)
32
+ end
33
+ @proposed_options.delete('cache')
34
+ @proposed_options.delete('cache_name')
35
+ if conversion_required?
36
+ @proposed_options['source-format'] =
37
+ File.open(@proposed_options['from_uri'].path) {|f| encoding(f)}
38
+ if @proposed_options['source-format'] == @proposed_options['target-format']
39
+ @proposed_options.delete('source-format')
40
+ @proposed_options.delete('target-format')
41
+ end
42
+ end
43
+ self
44
+ end
45
+
46
+ end
47
+
48
+ end