httpimagestore 1.7.0 → 1.8.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.
@@ -69,9 +69,12 @@ module Configuration
69
69
  attr_reader :image_name
70
70
  attr_reader :path_spec
71
71
 
72
- def initialize(global, image_name, path_spec, matcher)
72
+ def initialize(global, image_name, scheme, host, port, path_spec, matcher)
73
73
  @global = global
74
74
  @image_name = image_name
75
+ @scheme = scheme
76
+ @host = host
77
+ @port = port
75
78
  @path_spec = path_spec
76
79
  inclusion_matcher matcher
77
80
  end
@@ -79,26 +82,42 @@ module Configuration
79
82
  def store_path(request_state)
80
83
  store_path = request_state.images[@image_name].store_path or raise StorePathNotSetForImage.new(@image_name)
81
84
  return store_path unless @path_spec
82
- rendered_path(store_path, request_state)
83
- end
84
85
 
85
- def store_url(request_state)
86
- store_url = request_state.images[@image_name].store_url or raise StoreURLNotSetForImage.new(@image_name)
87
- return store_url unless @path_spec
88
- uri = URI(store_url)
89
- uri.path = '/' + URI.encode(rendered_path(URI.decode(uri.path), request_state))
90
- uri
86
+ locals = {
87
+ image_name: @image_name,
88
+ path: store_path
89
+ }
90
+
91
+ rendered_path(request_state.with_locals(locals))
91
92
  end
92
93
 
93
- private
94
+ def store_url(request_state)
95
+ url = request_state.images[@image_name].store_url or raise StoreURLNotSetForImage.new(@image_name)
96
+ url = url.dup
94
97
 
95
- def rendered_path(store_path, request_state)
96
- path = @global.paths[@path_spec]
97
98
  locals = {
98
- path: store_path,
99
- image_name: @image_name
99
+ image_name: @image_name,
100
+ path: URI.utf_decode(url.path),
101
+ url: url.to_s
100
102
  }
101
- Pathname.new(path.render(request_state.with_locals(locals))).cleanpath.to_s
103
+ locals[:scheme] = url.scheme if url.scheme
104
+ locals[:host] = url.host if url.host
105
+ locals[:port] = url.port if url.port
106
+
107
+ request_state = request_state.with_locals(locals)
108
+
109
+ # optional rewrites
110
+ url.scheme = request_state.render_template(@scheme) if @scheme
111
+ url.host = request_state.render_template(@host) if @host
112
+ url.port = request_state.render_template(@port).to_i if @port
113
+ url.path = URI.encode(rendered_path(request_state)).tap{|path| path.replace('/' + path) if path[0] != '/'} if @path_spec
114
+
115
+ url
116
+ end
117
+
118
+ private
119
+ def rendered_path(request_state)
120
+ Pathname.new(@global.paths[@path_spec].render(request_state)).cleanpath.to_s
102
121
  end
103
122
  end
104
123
 
@@ -106,9 +125,9 @@ module Configuration
106
125
  nodes = node.values.empty? ? node.children : [node]
107
126
  output_specs = nodes.map do |node|
108
127
  image_name = node.grab_values('image name').first
109
- path_spec, if_image_name_on = *node.grab_attributes('path', 'if-image-name-on')
128
+ scheme, host, port, path_spec, if_image_name_on = *node.grab_attributes('scheme', 'host', 'port', 'path', 'if-image-name-on')
110
129
  matcher = InclusionMatcher.new(image_name, if_image_name_on)
111
- OutputSpec.new(configuration.global, image_name, path_spec, matcher)
130
+ OutputSpec.new(configuration.global, image_name, scheme, host, port, path_spec, matcher)
112
131
  end
113
132
 
114
133
  configuration.output and raise StatementCollisionError.new(node, 'output')
@@ -214,5 +233,24 @@ module Configuration
214
233
  end
215
234
  end
216
235
  Handler::register_node_parser OutputStoreURL
236
+
237
+ class OutputStoreURI < OutputMultiBase
238
+ def self.match(node)
239
+ node.name == 'output_store_uri'
240
+ end
241
+
242
+ def realize(request_state)
243
+ urls = @output_specs.select do |output_spec|
244
+ output_spec.included?(request_state)
245
+ end.map do |output_spec|
246
+ output_spec.store_url(request_state).path
247
+ end
248
+
249
+ request_state.output do
250
+ write_url_list 200, urls
251
+ end
252
+ end
253
+ end
254
+ Handler::register_node_parser OutputStoreURI
217
255
  end
218
256
 
@@ -177,11 +177,11 @@ module Configuration
177
177
  end
178
178
 
179
179
  def private_url
180
- s3_object.url_for(:read, expires: 60 * 60 * 24 * 365 * 20).to_s # expire in 20 years
180
+ s3_object.url_for(:read, expires: 60 * 60 * 24 * 365 * 20) # expire in 20 years
181
181
  end
182
182
 
183
183
  def public_url
184
- s3_object.public_url.to_s
184
+ s3_object.public_url
185
185
  end
186
186
 
187
187
  def content_type
@@ -237,11 +237,19 @@ module Configuration
237
237
  end
238
238
 
239
239
  def private_url
240
- @cache_file.header['private_url'] ||= (dirty! :private_url; super)
240
+ url = @cache_file.header['private_url'] and return URI(url)
241
+ dirty! :private_url
242
+ url = super
243
+ @cache_file.header['private_url'] = url.to_s
244
+ url
241
245
  end
242
246
 
243
247
  def public_url
244
- @cache_file.header['public_url'] ||= (dirty! :public_url; super)
248
+ url = @cache_file.header['public_url'] and return URI(url)
249
+ dirty! :public_url
250
+ url = super
251
+ @cache_file.header['public_url'] = url.to_s
252
+ url
245
253
  end
246
254
 
247
255
  def content_type
@@ -1,10 +1,9 @@
1
1
  require_relative 'spec_helper'
2
2
  require 'httpimagestore/configuration'
3
- Configuration::Scope.logger = Logger.new('/dev/null')
3
+ MemoryLimit.logger = Configuration::Scope.logger = RootLogger.new('/dev/null')
4
4
 
5
5
  require 'httpimagestore/configuration/file'
6
6
  require 'httpimagestore/configuration/output'
7
- MemoryLimit.logger = Logger.new('/dev/null')
8
7
 
9
8
  describe Configuration do
10
9
  let :state do
@@ -54,7 +53,7 @@ describe Configuration do
54
53
  subject.handlers[0].sources[0].realize(state)
55
54
 
56
55
  state.images['original'].source_path.should == "test.in"
57
- state.images['original'].source_url.should == "file://test.in"
56
+ state.images['original'].source_url.to_s.should == "file:/test.in"
58
57
  end
59
58
 
60
59
  describe 'context locals' do
@@ -220,7 +219,7 @@ describe Configuration do
220
219
  subject.handlers[0].stores[0].realize(state)
221
220
 
222
221
  state.images['input'].store_path.should == "test.out"
223
- state.images['input'].store_url.should == "file://test.out"
222
+ state.images['input'].store_url.to_s.should == "file:/test.out"
224
223
  end
225
224
 
226
225
  describe 'conditional inclusion support' do
@@ -2,11 +2,10 @@ require_relative 'spec_helper'
2
2
  require_relative 'support/cuba_response_env'
3
3
 
4
4
  require 'httpimagestore/configuration'
5
- Configuration::Scope.logger = Logger.new('/dev/null')
5
+ MemoryLimit.logger = Configuration::Scope.logger = RootLogger.new('/dev/null')
6
6
 
7
7
  require 'httpimagestore/configuration/handler'
8
8
  require 'httpimagestore/configuration/output'
9
- MemoryLimit.logger = Logger.new('/dev/null')
10
9
 
11
10
  describe Configuration do
12
11
  describe Configuration::Handler do
@@ -1,11 +1,10 @@
1
1
  require_relative 'spec_helper'
2
2
  require 'httpimagestore/configuration'
3
- Configuration::Scope.logger = Logger.new('/dev/null')
3
+ MemoryLimit.logger = Configuration::Scope.logger = RootLogger.new('/dev/null')
4
4
 
5
5
  require 'httpimagestore/configuration/output'
6
6
  require 'httpimagestore/configuration/thumbnailer'
7
7
  require 'httpimagestore/configuration/identify'
8
- MemoryLimit.logger = Logger.new('/dev/null')
9
8
 
10
9
  describe Configuration do
11
10
  describe 'identify' do
@@ -2,11 +2,10 @@ require_relative 'spec_helper'
2
2
  require_relative 'support/cuba_response_env'
3
3
 
4
4
  require 'httpimagestore/configuration'
5
- Configuration::Scope.logger = Logger.new('/dev/null')
5
+ MemoryLimit.logger = Configuration::Scope.logger = RootLogger.new('/dev/null')
6
6
 
7
7
  require 'httpimagestore/configuration/output'
8
8
  require 'httpimagestore/configuration/file'
9
- MemoryLimit.logger = Logger.new('/dev/null')
10
9
 
11
10
  describe Configuration do
12
11
  let :state do
@@ -159,6 +158,10 @@ describe Configuration do
159
158
  end
160
159
 
161
160
  describe 'output store paths and URLs' do
161
+ let :utf_string do
162
+ (support_dir + 'utf_string.txt').read.strip
163
+ end
164
+
162
165
  let :in_file do
163
166
  Pathname.new("/tmp/test.in")
164
167
  end
@@ -179,8 +182,12 @@ describe Configuration do
179
182
  Pathname.new('/tmp/abc/t e s t.out')
180
183
  end
181
184
 
185
+ let :utf_test_file do
186
+ Pathname.new("/tmp/abc/#{utf_string}.out")
187
+ end
188
+
182
189
  before :each do
183
- test_file.dirname.mkdir
190
+ test_file.dirname.mkdir unless test_file.dirname.directory?
184
191
  test_file.open('w'){|io| io.write('abc')}
185
192
  space_test_file.open('w'){|io| io.write('abc')}
186
193
  in_file.open('w'){|io| io.write('abc')}
@@ -191,6 +198,7 @@ describe Configuration do
191
198
  after :each do
192
199
  test_file.exist? and test_file.unlink
193
200
  space_test_file.exist? and space_test_file.unlink
201
+ utf_test_file.exist? and utf_test_file.unlink
194
202
  test_file.dirname.exist? and test_file.dirname.rmdir
195
203
  out_file.unlink if out_file.exist?
196
204
  out2_file.unlink if out2_file.exist?
@@ -391,7 +399,7 @@ describe Configuration do
391
399
 
392
400
  env.instance_eval &state.output_callback
393
401
  env.res['Content-Type'].should == 'text/uri-list'
394
- env.res.data.should == "file://test.out\r\n"
402
+ env.res.data.should == "file:/test.out\r\n"
395
403
  end
396
404
 
397
405
  it 'should provide multiple file store URLs' do
@@ -423,7 +431,7 @@ describe Configuration do
423
431
 
424
432
  env.instance_eval &state.output_callback
425
433
  env.res['Content-Type'].should == 'text/uri-list'
426
- env.res.data.should == "file://test.out\r\nfile://test.out2\r\n"
434
+ env.res.data.should == "file:/test.out\r\nfile:/test.out2\r\n"
427
435
  end
428
436
 
429
437
  describe 'conditional inclusion support' do
@@ -468,12 +476,12 @@ describe Configuration do
468
476
 
469
477
  env.instance_eval &state.output_callback
470
478
  env.res['Content-Type'].should == 'text/uri-list'
471
- env.res.data.should == "file://test.out1\r\nfile://test.out3\r\n"
479
+ env.res.data.should == "file:/test.out1\r\nfile:/test.out3\r\n"
472
480
  end
473
481
  end
474
482
 
475
- describe 'custom formatting' do
476
- it 'should provide formatted file store URL' do
483
+ describe 'URL rewrites' do
484
+ it 'should allow using path spec to rewrite URL path component' do
477
485
  subject = Configuration.read(<<-'EOF')
478
486
  path "out" "abc/test.out"
479
487
 
@@ -492,7 +500,93 @@ describe Configuration do
492
500
 
493
501
  env.instance_eval &state.output_callback
494
502
  env.res['Content-Type'].should == 'text/uri-list'
495
- env.res.data.should == "file://abc/hello/world/test-xyz.out\r\n"
503
+ env.res.data.should == "file:/hello/abc/world/test-xyz.out\r\n"
504
+ end
505
+
506
+ it 'should allow rewriting scheme component' do
507
+ subject = Configuration.read(<<-'EOF')
508
+ path "out" "abc/test.out"
509
+
510
+ post "single" {
511
+ store_file "input" root="/tmp" path="out"
512
+
513
+ output_store_url "input" scheme="ftp"
514
+ }
515
+ EOF
516
+
517
+ subject.handlers[0].sources[0].realize(state)
518
+ subject.handlers[0].stores[0].realize(state)
519
+ subject.handlers[0].output.realize(state)
520
+
521
+ env.instance_eval &state.output_callback
522
+ env.res['Content-Type'].should == 'text/uri-list'
523
+ env.res.data.should == "ftp:/abc/test.out\r\n"
524
+ end
525
+
526
+ it 'should allow rewriting host component' do
527
+ subject = Configuration.read(<<-'EOF')
528
+ path "out" "abc/test.out"
529
+
530
+ post "single" {
531
+ store_file "input" root="/tmp" path="out"
532
+
533
+ output_store_url "input" host="localhost"
534
+ }
535
+ EOF
536
+
537
+ subject.handlers[0].sources[0].realize(state)
538
+ subject.handlers[0].stores[0].realize(state)
539
+ subject.handlers[0].output.realize(state)
540
+
541
+ env.instance_eval &state.output_callback
542
+ env.res['Content-Type'].should == 'text/uri-list'
543
+ env.res.data.should == "file://localhost/abc/test.out\r\n"
544
+ end
545
+
546
+ it 'should allow rewriting port component' do
547
+ subject = Configuration.read(<<-'EOF')
548
+ path "out" "abc/test.out"
549
+
550
+ post "single" {
551
+ store_file "input" root="/tmp" path="out"
552
+
553
+ output_store_url "input" port="21"
554
+ }
555
+ EOF
556
+
557
+ subject.handlers[0].sources[0].realize(state)
558
+ subject.handlers[0].stores[0].realize(state)
559
+ subject.handlers[0].output.realize(state)
560
+
561
+ env.instance_eval &state.output_callback
562
+ env.res['Content-Type'].should == 'text/uri-list'
563
+ env.res.data.should == "file::21/abc/test.out\r\n"
564
+ end
565
+
566
+ it 'should allow using variables for all supported rewrites' do
567
+ state = Configuration::RequestState.new('abc',
568
+ remote: 'example.com',
569
+ remote_port: 21,
570
+ proto: 'ftp'
571
+ )
572
+ subject = Configuration.read(<<-'EOF')
573
+ path "out" "abc/test.out"
574
+ path "formatted" "hello/#{dirname}/world/#{basename}-xyz.#{extension}"
575
+
576
+ post "single" {
577
+ store_file "input" root="/tmp" path="out"
578
+
579
+ output_store_url "input" scheme="#{proto}" host="#{remote}" port="#{remote_port}" path="formatted"
580
+ }
581
+ EOF
582
+
583
+ subject.handlers[0].sources[0].realize(state)
584
+ subject.handlers[0].stores[0].realize(state)
585
+ subject.handlers[0].output.realize(state)
586
+
587
+ env.instance_eval &state.output_callback
588
+ env.res['Content-Type'].should == 'text/uri-list'
589
+ env.res.data.should == "ftp://example.com:21/hello/abc/world/test-xyz.out\r\n"
496
590
  end
497
591
  end
498
592
 
@@ -518,7 +612,7 @@ describe Configuration do
518
612
 
519
613
  env.instance_eval &state.output_callback
520
614
  env.res['Content-Type'].should == 'text/uri-list'
521
- env.res.data.should == "file://abc/t%20e%20s%20t.out\r\nfile://abc/hello/world/t%20e%20s%20t-xyz.out\r\n"
615
+ env.res.data.should == "file:/abc/t%20e%20s%20t.out\r\nfile:/hello/abc/world/t%20e%20s%20t-xyz.out\r\n"
522
616
  end
523
617
  end
524
618
 
@@ -538,5 +632,190 @@ describe Configuration do
538
632
  end
539
633
  end
540
634
  end
635
+
636
+ describe Configuration::OutputStoreURI do
637
+ it 'should provide file store URI' do
638
+ subject = Configuration.read(<<-EOF)
639
+ path {
640
+ "out" "test.out"
641
+ }
642
+
643
+ post "single" {
644
+ store_file "input" root="/tmp" path="out"
645
+
646
+ output_store_uri "input"
647
+ }
648
+ EOF
649
+
650
+ subject.handlers[0].sources[0].realize(state)
651
+ subject.handlers[0].stores[0].realize(state)
652
+ subject.handlers[0].output.realize(state)
653
+
654
+ env.instance_eval &state.output_callback
655
+ env.res['Content-Type'].should == 'text/uri-list'
656
+ env.res.data.should == "/test.out\r\n"
657
+ end
658
+
659
+ it 'should provide multiple file store URIs' do
660
+ subject = Configuration.read(<<-EOF)
661
+ path {
662
+ "in" "test.in"
663
+ "out" "test.out"
664
+ "out2" "test.out2"
665
+ }
666
+
667
+ post "multi" {
668
+ source_file "original" root="/tmp" path="in"
669
+
670
+ store_file "input" root="/tmp" path="out"
671
+ store_file "original" root="/tmp" path="out2"
672
+
673
+ output_store_uri {
674
+ "input"
675
+ "original"
676
+ }
677
+ }
678
+ EOF
679
+
680
+ subject.handlers[0].sources[0].realize(state)
681
+ subject.handlers[0].sources[1].realize(state)
682
+ subject.handlers[0].stores[0].realize(state)
683
+ subject.handlers[0].stores[1].realize(state)
684
+ subject.handlers[0].output.realize(state)
685
+
686
+ env.instance_eval &state.output_callback
687
+ env.res['Content-Type'].should == 'text/uri-list'
688
+ env.res.data.should == "/test.out\r\n/test.out2\r\n"
689
+ end
690
+
691
+ describe 'conditional inclusion support' do
692
+ let :state do
693
+ Configuration::RequestState.new('abc', list: 'input,image2')
694
+ end
695
+
696
+ subject do
697
+ Configuration.read(<<-'EOF')
698
+ path {
699
+ "in" "test.in"
700
+ "out1" "test.out1"
701
+ "out2" "test.out2"
702
+ "out3" "test.out3"
703
+ }
704
+
705
+ post "multi" {
706
+ source_file "image1" root="/tmp" path="in"
707
+ source_file "image2" root="/tmp" path="in"
708
+
709
+ store_file "input" root="/tmp" path="out1"
710
+ store_file "image1" root="/tmp" path="out2"
711
+ store_file "image2" root="/tmp" path="out3"
712
+
713
+ output_store_uri {
714
+ "input" if-image-name-on="#{list}"
715
+ "image1" if-image-name-on="#{list}"
716
+ "image2" if-image-name-on="#{list}"
717
+ }
718
+ }
719
+ EOF
720
+ end
721
+
722
+ it 'should output store url only for images that names match if-image-name-on list' do
723
+ subject.handlers[0].sources[0].realize(state)
724
+ subject.handlers[0].sources[1].realize(state)
725
+ subject.handlers[0].sources[2].realize(state)
726
+ subject.handlers[0].stores[0].realize(state)
727
+ subject.handlers[0].stores[1].realize(state)
728
+ subject.handlers[0].stores[2].realize(state)
729
+ subject.handlers[0].output.realize(state)
730
+
731
+ env.instance_eval &state.output_callback
732
+ env.res['Content-Type'].should == 'text/uri-list'
733
+ env.res.data.should == "/test.out1\r\n/test.out3\r\n"
734
+ end
735
+ end
736
+
737
+ describe 'URI rewrites' do
738
+ it 'should allow using path spec to rewrite URI path' do
739
+ subject = Configuration.read(<<-'EOF')
740
+ path "out" "abc/test.out"
741
+
742
+ path "formatted" "hello/#{dirname}/world/#{basename}-xyz.#{extension}"
743
+
744
+ post "single" {
745
+ store_file "input" root="/tmp" path="out"
746
+
747
+ output_store_uri "input" path="formatted"
748
+ }
749
+ EOF
750
+
751
+ subject.handlers[0].sources[0].realize(state)
752
+ subject.handlers[0].stores[0].realize(state)
753
+ subject.handlers[0].output.realize(state)
754
+
755
+ env.instance_eval &state.output_callback
756
+ env.res['Content-Type'].should == 'text/uri-list'
757
+ env.res.data.should == "/hello/abc/world/test-xyz.out\r\n"
758
+ end
759
+ end
760
+
761
+ describe 'URI encoding' do
762
+ let :subject do
763
+ Configuration.read(<<-'EOF')
764
+ path "out" "abc/#{name}.out"
765
+ path "formatted" "hello/#{dirname}/world/#{basename}-xyz.#{extension}"
766
+
767
+ post "single" {
768
+ store_file "input" root="/tmp" path="out"
769
+
770
+ output_store_uri {
771
+ "input"
772
+ "input" path="formatted"
773
+ }
774
+ }
775
+ EOF
776
+ end
777
+
778
+ it 'should provide properly encoded file store URI' do
779
+ state = Configuration::RequestState.new('abc', name: 't e s t')
780
+
781
+ subject.handlers[0].sources[0].realize(state)
782
+ subject.handlers[0].stores[0].realize(state)
783
+ subject.handlers[0].output.realize(state)
784
+
785
+ env.instance_eval &state.output_callback
786
+ env.res['Content-Type'].should == 'text/uri-list'
787
+ env.res.data.should == "/abc/t%20e%20s%20t.out\r\n/hello/abc/world/t%20e%20s%20t-xyz.out\r\n"
788
+ end
789
+
790
+ it 'should handle UTF-8 characters' do
791
+ state = Configuration::RequestState.new('abc', name: utf_string)
792
+ subject.handlers[0].sources[0].realize(state)
793
+ subject.handlers[0].stores[0].realize(state)
794
+ subject.handlers[0].output.realize(state)
795
+
796
+ env.instance_eval &state.output_callback
797
+ env.res['Content-Type'].should == 'text/uri-list'
798
+ l1, l2 = *env.res.data.split("\r\n")
799
+ URI.utf_decode(l1).should == "/abc/#{utf_string}.out"
800
+ URI.utf_decode(l2).should == "/hello/abc/world/#{utf_string}-xyz.out"
801
+ end
802
+ end
803
+
804
+ describe 'error handling' do
805
+ it 'should raise StoreURLNotSetForImage for output of not stored image' do
806
+ subject = Configuration.read(<<-EOF)
807
+ post "single" {
808
+ output_store_uri "input"
809
+ }
810
+ EOF
811
+
812
+ subject.handlers[0].sources[0].realize(state)
813
+
814
+ expect {
815
+ subject.handlers[0].output.realize(state)
816
+ }.to raise_error Configuration::StoreURLNotSetForImage, %{store URL not set for image 'input'}
817
+ end
818
+ end
819
+ end
541
820
  end
542
821
  end
@@ -1,10 +1,9 @@
1
1
  require_relative 'spec_helper'
2
2
  require 'httpimagestore/configuration'
3
- Configuration::Scope.logger = Logger.new('/dev/null')
3
+ MemoryLimit.logger = Configuration::Scope.logger = RootLogger.new('/dev/null')
4
4
 
5
5
  require 'httpimagestore/configuration/handler'
6
6
  require 'httpimagestore/configuration/path'
7
- MemoryLimit.logger = Logger.new('/dev/null')
8
7
 
9
8
  describe Configuration do
10
9
  describe 'path rendering' do
@@ -62,7 +61,7 @@ describe Configuration do
62
61
 
63
62
  expect {
64
63
  subject.paths['test'].render
65
- }.to raise_error Configuration::NoValueForPathTemplatePlaceholerError, %q{cannot generate path 'test' from template '#{abc}#{xyz}': no value for '#{abc}'}
64
+ }.to raise_error Configuration::NoValueForPathTemplatePlaceholerError, %q{cannot generate path 'test' from template '#{abc}#{xyz}': no value for '#{abc}'}
66
65
  end
67
66
  end
68
67