samlown-carrierwave 0.4.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.
Files changed (111) hide show
  1. data/Generators +4 -0
  2. data/History.txt +125 -0
  3. data/Manifest.txt +110 -0
  4. data/README.rdoc +524 -0
  5. data/Rakefile +39 -0
  6. data/carrierwave.gemspec +85 -0
  7. data/cucumber.yml +2 -0
  8. data/features/caching.feature +28 -0
  9. data/features/download.feature +20 -0
  10. data/features/file_storage.feature +37 -0
  11. data/features/file_storage_overridden_filename.feature +38 -0
  12. data/features/file_storage_overridden_store_dir.feature +38 -0
  13. data/features/file_storage_reversing_processor.feature +43 -0
  14. data/features/fixtures/bork.txt +1 -0
  15. data/features/fixtures/monkey.txt +1 -0
  16. data/features/grid_fs_storage.feature +32 -0
  17. data/features/mount_activerecord.feature +46 -0
  18. data/features/mount_datamapper.feature +46 -0
  19. data/features/step_definitions/activerecord_steps.rb +22 -0
  20. data/features/step_definitions/caching_steps.rb +14 -0
  21. data/features/step_definitions/datamapper_steps.rb +29 -0
  22. data/features/step_definitions/download_steps.rb +4 -0
  23. data/features/step_definitions/file_steps.rb +53 -0
  24. data/features/step_definitions/general_steps.rb +85 -0
  25. data/features/step_definitions/mount_steps.rb +19 -0
  26. data/features/step_definitions/store_steps.rb +18 -0
  27. data/features/support/activerecord.rb +30 -0
  28. data/features/support/datamapper.rb +7 -0
  29. data/features/support/env.rb +22 -0
  30. data/features/versions_basics.feature +50 -0
  31. data/features/versions_nested_versions.feature +70 -0
  32. data/features/versions_overridden_filename.feature +51 -0
  33. data/features/versions_overriden_store_dir.feature +41 -0
  34. data/lib/carrierwave.rb +98 -0
  35. data/lib/carrierwave/compatibility/paperclip.rb +95 -0
  36. data/lib/carrierwave/core_ext/blank.rb +46 -0
  37. data/lib/carrierwave/core_ext/file.rb +11 -0
  38. data/lib/carrierwave/core_ext/inheritable_attributes.rb +108 -0
  39. data/lib/carrierwave/core_ext/module_setup.rb +51 -0
  40. data/lib/carrierwave/mount.rb +359 -0
  41. data/lib/carrierwave/orm/activerecord.rb +73 -0
  42. data/lib/carrierwave/orm/datamapper.rb +27 -0
  43. data/lib/carrierwave/orm/mongoid.rb +23 -0
  44. data/lib/carrierwave/orm/mongomapper.rb +27 -0
  45. data/lib/carrierwave/orm/sequel.rb +45 -0
  46. data/lib/carrierwave/processing/image_science.rb +101 -0
  47. data/lib/carrierwave/processing/mini_magick.rb +265 -0
  48. data/lib/carrierwave/processing/rmagick.rb +282 -0
  49. data/lib/carrierwave/sanitized_file.rb +273 -0
  50. data/lib/carrierwave/storage/abstract.rb +30 -0
  51. data/lib/carrierwave/storage/cloud_files.rb +169 -0
  52. data/lib/carrierwave/storage/file.rb +48 -0
  53. data/lib/carrierwave/storage/grid_fs.rb +97 -0
  54. data/lib/carrierwave/storage/right_s3.rb +3 -0
  55. data/lib/carrierwave/storage/s3.rb +206 -0
  56. data/lib/carrierwave/test/matchers.rb +128 -0
  57. data/lib/carrierwave/uploader.rb +44 -0
  58. data/lib/carrierwave/uploader/cache.rb +145 -0
  59. data/lib/carrierwave/uploader/callbacks.rb +42 -0
  60. data/lib/carrierwave/uploader/configuration.rb +132 -0
  61. data/lib/carrierwave/uploader/default_url.rb +19 -0
  62. data/lib/carrierwave/uploader/download.rb +59 -0
  63. data/lib/carrierwave/uploader/extension_whitelist.rb +37 -0
  64. data/lib/carrierwave/uploader/mountable.rb +39 -0
  65. data/lib/carrierwave/uploader/processing.rb +83 -0
  66. data/lib/carrierwave/uploader/proxy.rb +62 -0
  67. data/lib/carrierwave/uploader/remove.rb +22 -0
  68. data/lib/carrierwave/uploader/store.rb +89 -0
  69. data/lib/carrierwave/uploader/url.rb +33 -0
  70. data/lib/carrierwave/uploader/versions.rb +146 -0
  71. data/merb_generators/uploader_generator.rb +22 -0
  72. data/rails_generators/uploader/USAGE +2 -0
  73. data/rails_generators/uploader/templates/uploader.rb +47 -0
  74. data/rails_generators/uploader/uploader_generator.rb +21 -0
  75. data/script/console +10 -0
  76. data/script/destroy +14 -0
  77. data/script/generate +14 -0
  78. data/spec/compatibility/paperclip_spec.rb +52 -0
  79. data/spec/fixtures/bork.txt +1 -0
  80. data/spec/fixtures/landscape.jpg +0 -0
  81. data/spec/fixtures/portrait.jpg +0 -0
  82. data/spec/fixtures/test.jpeg +1 -0
  83. data/spec/fixtures/test.jpg +1 -0
  84. data/spec/mount_spec.rb +538 -0
  85. data/spec/orm/activerecord_spec.rb +271 -0
  86. data/spec/orm/datamapper_spec.rb +168 -0
  87. data/spec/orm/mongoid_spec.rb +202 -0
  88. data/spec/orm/mongomapper_spec.rb +202 -0
  89. data/spec/orm/sequel_spec.rb +183 -0
  90. data/spec/processing/image_science_spec.rb +56 -0
  91. data/spec/processing/mini_magick_spec.rb +76 -0
  92. data/spec/processing/rmagick_spec.rb +75 -0
  93. data/spec/sanitized_file_spec.rb +623 -0
  94. data/spec/spec_helper.rb +92 -0
  95. data/spec/storage/cloudfiles_spec.rb +78 -0
  96. data/spec/storage/grid_fs_spec.rb +83 -0
  97. data/spec/storage/s3_spec.rb +118 -0
  98. data/spec/uploader/cache_spec.rb +209 -0
  99. data/spec/uploader/configuration_spec.rb +105 -0
  100. data/spec/uploader/default_url_spec.rb +85 -0
  101. data/spec/uploader/download_spec.rb +75 -0
  102. data/spec/uploader/extension_whitelist_spec.rb +44 -0
  103. data/spec/uploader/mountable_spec.rb +33 -0
  104. data/spec/uploader/paths_spec.rb +22 -0
  105. data/spec/uploader/processing_spec.rb +73 -0
  106. data/spec/uploader/proxy_spec.rb +54 -0
  107. data/spec/uploader/remove_spec.rb +70 -0
  108. data/spec/uploader/store_spec.rb +264 -0
  109. data/spec/uploader/url_spec.rb +102 -0
  110. data/spec/uploader/versions_spec.rb +298 -0
  111. metadata +433 -0
@@ -0,0 +1,282 @@
1
+ # encoding: utf-8
2
+
3
+ unless defined? Magick
4
+ begin
5
+ require 'rmagick'
6
+ rescue LoadError
7
+ require 'RMagick'
8
+ rescue LoadError
9
+ puts "WARNING: Failed to require rmagick, image processing may fail!"
10
+ end
11
+ end
12
+
13
+ module CarrierWave
14
+
15
+ ##
16
+ # This module simplifies manipulation with RMagick by providing a set
17
+ # of convenient helper methods. If you want to use them, you'll need to
18
+ # require this file:
19
+ #
20
+ # require 'carrierwave/processing/rmagick'
21
+ #
22
+ # And then include it in your uploader:
23
+ #
24
+ # class MyUploader < CarrierWave::Uploader::Base
25
+ # include CarrierWave::RMagick
26
+ # end
27
+ #
28
+ # You can now use the provided helpers:
29
+ #
30
+ # class MyUploader < CarrierWave::Uploader::Base
31
+ # include CarrierWave::RMagick
32
+ #
33
+ # process :resize_to_fit => [200, 200]
34
+ # end
35
+ #
36
+ # Or create your own helpers with the powerful manipulate! method. Check
37
+ # out the RMagick docs at http://www.imagemagick.org/RMagick/doc/ for more
38
+ # info
39
+ #
40
+ # class MyUploader < CarrierWave::Uploader::Base
41
+ # include CarrierWave::RMagick
42
+ #
43
+ # process :do_stuff => 10.0
44
+ #
45
+ # def do_stuff(blur_factor)
46
+ # manipulate! do |img|
47
+ # img = img.sepiatone
48
+ # img = img.auto_orient
49
+ # img = img.radial_blur(blur_factor)
50
+ # end
51
+ # end
52
+ # end
53
+ #
54
+ # === Note
55
+ #
56
+ # You should be aware how RMagick handles memory. manipulate! takes care
57
+ # of freeing up memory for you, but for optimum memory usage you should
58
+ # use destructive operations as much as possible:
59
+ #
60
+ # DON'T DO THIS:
61
+ # img = img.resize_to_fit
62
+ #
63
+ # DO THIS INSTEAD:
64
+ # img.resize_to_fit!
65
+ #
66
+ # Read this for more information why:
67
+ #
68
+ # http://rubyforge.org/forum/forum.php?thread_id=1374&forum_id=1618
69
+ #
70
+ module RMagick
71
+
72
+ def self.included(base)
73
+ super
74
+ base.extend(ClassMethods)
75
+ end
76
+
77
+ module ClassMethods
78
+ def convert(format)
79
+ process :convert => format
80
+ end
81
+
82
+ def resize_to_limit(width, height)
83
+ process :resize_to_limit => [width, height]
84
+ end
85
+
86
+ def resize_to_fit(width, height)
87
+ process :resize_to_fit => [width, height]
88
+ end
89
+
90
+ def resize_to_fill(width, height, gravity=::Magick::CenterGravity)
91
+ process :resize_to_fill => [width, height, gravity]
92
+ end
93
+
94
+ def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
95
+ process :resize_and_pad => [width, height, background, gravity]
96
+ end
97
+ end
98
+
99
+ ##
100
+ # Changes the image encoding format to the given format
101
+ #
102
+ # See even http://www.imagemagick.org/RMagick/doc/magick.html#formats
103
+ #
104
+ # === Parameters
105
+ #
106
+ # [format (#to_s)] an abreviation of the format
107
+ #
108
+ # === Yields
109
+ #
110
+ # [Magick::Image] additional manipulations to perform
111
+ #
112
+ # === Examples
113
+ #
114
+ # image.convert(:png)
115
+ #
116
+ def convert(format)
117
+ manipulate!(:format => format)
118
+ end
119
+
120
+ ##
121
+ # Resize the image to fit within the specified dimensions while retaining
122
+ # the original aspect ratio. Will only resize the image if it is larger than the
123
+ # specified dimensions. The resulting image may be shorter or narrower than specified
124
+ # in the smaller dimension but will not be larger than the specified values.
125
+ #
126
+ # === Parameters
127
+ #
128
+ # [width (Integer)] the width to scale the image to
129
+ # [height (Integer)] the height to scale the image to
130
+ #
131
+ # === Yields
132
+ #
133
+ # [Magick::Image] additional manipulations to perform
134
+ #
135
+ def resize_to_limit(width, height)
136
+ manipulate! do |img|
137
+ geometry = Magick::Geometry.new(width, height, 0, 0, Magick::GreaterGeometry)
138
+ new_img = img.change_geometry(geometry) do |new_width, new_height|
139
+ img.resize(new_width, new_height)
140
+ end
141
+ destroy_image(img)
142
+ new_img = yield(new_img) if block_given?
143
+ new_img
144
+ end
145
+ end
146
+
147
+ ##
148
+ # From the RMagick documentation: "Resize the image to fit within the
149
+ # specified dimensions while retaining the original aspect ratio. The
150
+ # image may be shorter or narrower than specified in the smaller dimension
151
+ # but will not be larger than the specified values."
152
+ #
153
+ # See even http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fit
154
+ #
155
+ # === Parameters
156
+ #
157
+ # [width (Integer)] the width to scale the image to
158
+ # [height (Integer)] the height to scale the image to
159
+ #
160
+ # === Yields
161
+ #
162
+ # [Magick::Image] additional manipulations to perform
163
+ #
164
+ def resize_to_fit(width, height)
165
+ manipulate! do |img|
166
+ img.resize_to_fit!(width, height)
167
+ img = yield(img) if block_given?
168
+ img
169
+ end
170
+ end
171
+
172
+ ##
173
+ # From the RMagick documentation: "Resize the image to fit within the
174
+ # specified dimensions while retaining the aspect ratio of the original
175
+ # image. If necessary, crop the image in the larger dimension."
176
+ #
177
+ # See even http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fill
178
+ #
179
+ # === Parameters
180
+ #
181
+ # [width (Integer)] the width to scale the image to
182
+ # [height (Integer)] the height to scale the image to
183
+ #
184
+ # === Yields
185
+ #
186
+ # [Magick::Image] additional manipulations to perform
187
+ #
188
+ def resize_to_fill(width, height, gravity=::Magick::CenterGravity)
189
+ manipulate! do |img|
190
+ img.crop_resized!(width, height, gravity)
191
+ img = yield(img) if block_given?
192
+ img
193
+ end
194
+ end
195
+
196
+ ##
197
+ # Resize the image to fit within the specified dimensions while retaining
198
+ # the original aspect ratio. If necessary, will pad the remaining area
199
+ # with the given color, which defaults to transparent (for gif and png,
200
+ # white for jpeg).
201
+ #
202
+ # === Parameters
203
+ #
204
+ # [width (Integer)] the width to scale the image to
205
+ # [height (Integer)] the height to scale the image to
206
+ # [background (String, :transparent)] the color of the background as a hexcode, like "#ff45de"
207
+ # [gravity (Magick::GravityType)] how to position the image
208
+ #
209
+ # === Yields
210
+ #
211
+ # [Magick::Image] additional manipulations to perform
212
+ #
213
+ def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
214
+ manipulate! do |img|
215
+ img.resize_to_fit!(width, height)
216
+ new_img = ::Magick::Image.new(width, height)
217
+ if background == :transparent
218
+ filled = new_img.matte_floodfill(1, 1)
219
+ else
220
+ filled = new_img.color_floodfill(1, 1, ::Magick::Pixel.from_color(background))
221
+ end
222
+ destroy_image(new_img)
223
+ filled.composite!(img, gravity, ::Magick::OverCompositeOp)
224
+ destroy_image(img)
225
+ filled = yield(filled) if block_given?
226
+ filled
227
+ end
228
+ end
229
+
230
+ ##
231
+ # Manipulate the image with RMagick. This method will load up an image
232
+ # and then pass each of its frames to the supplied block. It will then
233
+ # save the image to disk.
234
+ #
235
+ # === Gotcha
236
+ #
237
+ # This method assumes that the object responds to +current_path+.
238
+ # Any class that this module is mixed into must have a +current_path+ method.
239
+ # CarrierWave::Uploader does, so you won't need to worry about this in
240
+ # most cases.
241
+ #
242
+ # === Yields
243
+ #
244
+ # [Magick::Image] manipulations to perform
245
+ #
246
+ # === Raises
247
+ #
248
+ # [CarrierWave::ProcessingError] if manipulation failed.
249
+ #
250
+ def manipulate!(options={})
251
+ image = ::Magick::Image.read(current_path)
252
+
253
+ frames = if image.size > 1
254
+ list = ::Magick::ImageList.new
255
+ image.each do |frame|
256
+ list << yield( frame )
257
+ end
258
+ list
259
+ else
260
+ frame = image.first
261
+ frame = yield( frame ) if block_given?
262
+ frame
263
+ end
264
+
265
+ if options[:format]
266
+ frames.write("#{options[:format]}:#{current_path}")
267
+ else
268
+ frames.write(current_path)
269
+ end
270
+ destroy_image(frames)
271
+ rescue ::Magick::ImageMagickError => e
272
+ raise CarrierWave::ProcessingError.new("Failed to manipulate with rmagick, maybe it is not an image? Original Error: #{e}")
273
+ end
274
+
275
+ private
276
+
277
+ def destroy_image(image)
278
+ image.destroy! if image.respond_to?(:destroy!)
279
+ end
280
+
281
+ end # RMagick
282
+ end # CarrierWave
@@ -0,0 +1,273 @@
1
+ # encoding: utf-8
2
+
3
+ require 'pathname'
4
+
5
+ module CarrierWave
6
+
7
+ ##
8
+ # SanitizedFile is a base class which provides a common API around all
9
+ # the different quirky Ruby File libraries. It has support for Tempfile,
10
+ # File, StringIO, Merb-style upload Hashes, as well as paths given as
11
+ # Strings and Pathnames.
12
+ #
13
+ # It's probably needlessly comprehensive and complex. Help is appreciated.
14
+ #
15
+ class SanitizedFile
16
+
17
+ attr_accessor :file
18
+
19
+ def initialize(file)
20
+ self.file = file
21
+ end
22
+
23
+ ##
24
+ # Returns the filename as is, without sanizting it.
25
+ #
26
+ # === Returns
27
+ #
28
+ # [String] the unsanitized filename
29
+ #
30
+ def original_filename
31
+ return @original_filename if @original_filename
32
+ if @file and @file.respond_to?(:original_filename)
33
+ @file.original_filename
34
+ elsif path
35
+ File.basename(path)
36
+ end
37
+ end
38
+
39
+ ##
40
+ # Returns the filename, sanitized to strip out any evil characters.
41
+ #
42
+ # === Returns
43
+ #
44
+ # [String] the sanitized filename
45
+ #
46
+ def filename
47
+ sanitize(original_filename) if original_filename
48
+ end
49
+
50
+ alias_method :identifier, :filename
51
+
52
+ ##
53
+ # Returns the part of the filename before the extension. So if a file is called 'test.jpeg'
54
+ # this would return 'test'
55
+ #
56
+ # === Returns
57
+ #
58
+ # [String] the first part of the filename
59
+ #
60
+ def basename
61
+ split_extension(filename)[0] if filename
62
+ end
63
+
64
+ ##
65
+ # Returns the file extension
66
+ #
67
+ # === Returns
68
+ #
69
+ # [String] the extension
70
+ #
71
+ def extension
72
+ split_extension(filename)[1] if filename
73
+ end
74
+
75
+ ##
76
+ # Returns the file's size.
77
+ #
78
+ # === Returns
79
+ #
80
+ # [Integer] the file's size in bytes.
81
+ #
82
+ def size
83
+ if is_path?
84
+ exists? ? File.size(path) : 0
85
+ elsif @file.respond_to?(:size)
86
+ @file.size
87
+ elsif path
88
+ exists? ? File.size(path) : 0
89
+ else
90
+ 0
91
+ end
92
+ end
93
+
94
+ ##
95
+ # Returns the full path to the file. If the file has no path, it will return nil.
96
+ #
97
+ # === Returns
98
+ #
99
+ # [String, nil] the path where the file is located.
100
+ #
101
+ def path
102
+ unless @file.blank?
103
+ if is_path?
104
+ File.expand_path(@file)
105
+ elsif @file.respond_to?(:path) and not @file.path.blank?
106
+ File.expand_path(@file.path)
107
+ end
108
+ end
109
+ end
110
+
111
+ ##
112
+ # === Returns
113
+ #
114
+ # [Boolean] whether the file is supplied as a pathname or string.
115
+ #
116
+ def is_path?
117
+ !!((@file.is_a?(String) || @file.is_a?(Pathname)) && !@file.blank?)
118
+ end
119
+
120
+ ##
121
+ # === Returns
122
+ #
123
+ # [Boolean] whether the file is valid and has a non-zero size
124
+ #
125
+ def empty?
126
+ @file.nil? || self.size.nil? || self.size.zero?
127
+ end
128
+
129
+ alias_method :blank?, :empty?
130
+
131
+ ##
132
+ # === Returns
133
+ #
134
+ # [Boolean] Whether the file exists
135
+ #
136
+ def exists?
137
+ return File.exists?(self.path) if self.path
138
+ return false
139
+ end
140
+
141
+ ##
142
+ # Returns the contents of the file.
143
+ #
144
+ # === Returns
145
+ #
146
+ # [String] contents of the file
147
+ #
148
+ def read
149
+ if is_path?
150
+ File.read(@file)
151
+ else
152
+ @file.rewind if @file.respond_to?(:rewind)
153
+ @file.read
154
+ end
155
+ end
156
+
157
+ ##
158
+ # Moves the file to the given path
159
+ #
160
+ # === Parameters
161
+ #
162
+ # [new_path (String)] The path where the file should be moved.
163
+ # [permissions (Integer)] permissions to set on the file in its new location.
164
+ #
165
+ def move_to(new_path, permissions=nil)
166
+ return if self.empty?
167
+ new_path = File.expand_path(new_path)
168
+
169
+ mkdir!(new_path)
170
+ if exists?
171
+ FileUtils.mv(path, new_path) unless new_path == path
172
+ else
173
+ File.open(new_path, "wb") { |f| f.write(read) }
174
+ end
175
+ chmod!(new_path, permissions)
176
+ self.file = new_path
177
+ end
178
+
179
+ ##
180
+ # Creates a copy of this file and moves it to the given path. Returns the copy.
181
+ #
182
+ # === Parameters
183
+ #
184
+ # [new_path (String)] The path where the file should be copied to.
185
+ # [permissions (Integer)] permissions to set on the copy
186
+ #
187
+ # === Returns
188
+ #
189
+ # @return [CarrierWave::SanitizedFile] the location where the file will be stored.
190
+ #
191
+ def copy_to(new_path, permissions=nil)
192
+ return if self.empty?
193
+ new_path = File.expand_path(new_path)
194
+
195
+ mkdir!(new_path)
196
+ if exists?
197
+ FileUtils.cp(path, new_path) unless new_path == path
198
+ else
199
+ File.open(new_path, "wb") { |f| f.write(read) }
200
+ end
201
+ chmod!(new_path, permissions)
202
+ self.class.new(new_path)
203
+ end
204
+
205
+ ##
206
+ # Removes the file from the filesystem.
207
+ #
208
+ def delete
209
+ FileUtils.rm(self.path) if exists?
210
+ end
211
+
212
+ ##
213
+ # Returns the content type of the file.
214
+ #
215
+ # === Returns
216
+ #
217
+ # [String] the content type of the file
218
+ #
219
+ def content_type
220
+ return @content_type if @content_type
221
+ @file.content_type.chomp if @file.respond_to?(:content_type) and @file.content_type
222
+ end
223
+
224
+ private
225
+
226
+ def file=(file)
227
+ if file.is_a?(Hash)
228
+ @file = file["tempfile"] || file[:tempfile]
229
+ @original_filename = file["filename"] || file[:filename]
230
+ @content_type = file["content_type"] || file[:content_type]
231
+ else
232
+ @file = file
233
+ @original_filename = nil
234
+ @content_type = nil
235
+ end
236
+ end
237
+
238
+ # create the directory if it doesn't exist
239
+ def mkdir!(path)
240
+ FileUtils.mkdir_p(File.dirname(path)) unless File.exists?(File.dirname(path))
241
+ end
242
+
243
+ def chmod!(path, permissions)
244
+ File.chmod(permissions, path) if permissions
245
+ end
246
+
247
+ # Sanitize the filename, to prevent hacking
248
+ def sanitize(name)
249
+ name = name.gsub("\\", "/") # work-around for IE
250
+ name = File.basename(name)
251
+ name = name.gsub(/[^a-zA-Z0-9\.\-\+_]/,"_")
252
+ name = "_#{name}" if name =~ /\A\.+\z/
253
+ name = "unnamed" if name.size == 0
254
+ return name.downcase
255
+ end
256
+
257
+ def split_extension(filename)
258
+ # regular expressions to try for identifying extensions
259
+ extension_matchers = [
260
+ /\A(.+)\.(tar\.gz)\z/, # matches "something.tar.gz"
261
+ /\A(.+)\.([^\.]+)\z/ # matches "something.jpg"
262
+ ]
263
+
264
+ extension_matchers.each do |regexp|
265
+ if filename =~ regexp
266
+ return $1, $2
267
+ end
268
+ end
269
+ return filename, "" # In case we weren't able to split the extension
270
+ end
271
+
272
+ end # SanitizedFile
273
+ end # CarrierWave