spud_media 0.9.5 → 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- data/app/assets/javascripts/spud/admin/media/picker.js +13 -2
- data/app/assets/javascripts/spud/admin/media/plugin.js +1 -0
- data/app/assets/libs/jcrop/js/jquery.Jcrop.js +26 -26
- data/app/controllers/spud/admin/media_controller.rb +7 -4
- data/app/models/spud_media.rb +19 -14
- data/app/views/spud/admin/media/index.html.erb +4 -3
- data/app/views/spud/admin/media/replace.html.erb +26 -0
- data/config/routes.rb +5 -4
- data/lib/spud_media/version.rb +1 -1
- metadata +6 -5
@@ -1,5 +1,5 @@
|
|
1
1
|
spud.admin.mediapicker = new function(){
|
2
|
-
|
2
|
+
|
3
3
|
var self = this;
|
4
4
|
var supportsHtml5Upload = false;
|
5
5
|
var selectedFile = {};
|
@@ -18,6 +18,7 @@ spud.admin.mediapicker = new function(){
|
|
18
18
|
$('.spud_media_picker_tabs a').first().click();
|
19
19
|
$('.spud_media_picker_tab_advanced').on('spud_media_picker_tab_activated', self.activatedAdvancedTab);
|
20
20
|
$('.spud_media_picker_option_dimensions').on('blur', 'input', self.dimensionsChanged);
|
21
|
+
$('.spud_media_picker_option').on('keyup', 'input[type=text]', self.pickerOptionKeyDown);
|
21
22
|
};
|
22
23
|
|
23
24
|
self.clickedTab = function(e){
|
@@ -150,6 +151,7 @@ spud.admin.mediapicker = new function(){
|
|
150
151
|
else{
|
151
152
|
$('.spud_media_picker_option_target').show();
|
152
153
|
$('.spud_media_picker_option_text').show();
|
154
|
+
$('.spud_media_picker_option_text input').val(tinyMCEPopup.editor.selection.getContent());
|
153
155
|
$('.spud_media_picker_option_float').hide();
|
154
156
|
$('.spud_media_picker_option_title').hide();
|
155
157
|
$('.spud_media_picker_option_dimensions').hide();
|
@@ -162,7 +164,6 @@ spud.admin.mediapicker = new function(){
|
|
162
164
|
img.onload = function(){
|
163
165
|
_originalWidth = img.width;
|
164
166
|
_originalHeight = img.height;
|
165
|
-
console.log(_originalWidth, _originalHeight);
|
166
167
|
};
|
167
168
|
img.src = url;
|
168
169
|
};
|
@@ -201,8 +202,18 @@ spud.admin.mediapicker = new function(){
|
|
201
202
|
else{
|
202
203
|
selectedFile.target = $('select[name="spud_media_picker_option_target"]').val();
|
203
204
|
selectedFile.text = $('input[name="spud_media_picker_option_text"]').val();
|
205
|
+
if(!selectedFile.text){
|
206
|
+
window.alert("Link Text is a required field.");
|
207
|
+
return;
|
208
|
+
}
|
204
209
|
}
|
205
210
|
tinyMCEPopup.editor.execCommand('spudMediaInsertSelected', false, selectedFile);
|
206
211
|
tinyMCEPopup.close();
|
207
212
|
};
|
213
|
+
|
214
|
+
self.pickerOptionKeyDown = function(e){
|
215
|
+
if(e.keyCode == 13){
|
216
|
+
self.clickedInsert(e);
|
217
|
+
}
|
218
|
+
};
|
208
219
|
};
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
2
|
* jquery.Jcrop.js v0.9.10
|
3
|
-
* jQuery Image Cropping Plugin - released under MIT License
|
3
|
+
* jQuery Image Cropping Plugin - released under MIT License
|
4
4
|
* Author: Kelly Hallman <khallman@gmail.com>
|
5
5
|
* http://github.com/tapmodo/Jcrop
|
6
6
|
* Copyright (c) 2008-2012 Tapmodo Interactive LLC {{{
|
@@ -168,7 +168,7 @@
|
|
168
168
|
if ((ord === 'move') && !options.allowMove) {
|
169
169
|
return false;
|
170
170
|
}
|
171
|
-
|
171
|
+
|
172
172
|
// Fix position of crop area when dragged the very first time.
|
173
173
|
// Necessary when crop image is in a hidden element when page is loaded.
|
174
174
|
docOffset = getPos($img);
|
@@ -254,7 +254,7 @@
|
|
254
254
|
function newTracker() //{{{
|
255
255
|
{
|
256
256
|
var trk = $('<div></div>').addClass(cssClass('tracker'));
|
257
|
-
if (
|
257
|
+
if (navigator.userAgent.match(/msie/i)) {
|
258
258
|
trk.css({
|
259
259
|
opacity: 0,
|
260
260
|
backgroundColor: 'white'
|
@@ -267,9 +267,9 @@
|
|
267
267
|
// }}}
|
268
268
|
// Initialization {{{
|
269
269
|
// Sanitize some options {{{
|
270
|
-
|
271
|
-
|
272
|
-
|
270
|
+
ie6mode = ( navigator.userAgent.match(/msie/i) && navigator.userAgent.match(/6/) );
|
271
|
+
|
272
|
+
|
273
273
|
if (typeof(obj) !== 'object') {
|
274
274
|
obj = $(obj)[0];
|
275
275
|
}
|
@@ -304,12 +304,12 @@
|
|
304
304
|
$origimg.width($origimg[0].width);
|
305
305
|
$origimg.height($origimg[0].height);
|
306
306
|
} else {
|
307
|
-
// Obtain dimensions from temporary image in case the original is not loaded yet (e.g. IE 7.0).
|
307
|
+
// Obtain dimensions from temporary image in case the original is not loaded yet (e.g. IE 7.0).
|
308
308
|
var tempImage = new Image();
|
309
309
|
tempImage.src = $origimg[0].src;
|
310
310
|
$origimg.width(tempImage.width);
|
311
311
|
$origimg.height(tempImage.height);
|
312
|
-
}
|
312
|
+
}
|
313
313
|
|
314
314
|
var $img = $origimg.clone().removeAttr('id').css(img_css).show();
|
315
315
|
|
@@ -327,8 +327,8 @@
|
|
327
327
|
|
328
328
|
var boundx = $img.width(),
|
329
329
|
boundy = $img.height(),
|
330
|
-
|
331
|
-
|
330
|
+
|
331
|
+
|
332
332
|
$div = $('<div />').width(boundx).height(boundy).addClass(cssClass('holder')).css({
|
333
333
|
position: 'relative',
|
334
334
|
backgroundColor: options.bgColor
|
@@ -340,24 +340,24 @@
|
|
340
340
|
|
341
341
|
var $img2 = $('<div />'),
|
342
342
|
|
343
|
-
$img_holder = $('<div />')
|
343
|
+
$img_holder = $('<div />')
|
344
344
|
.width('100%').height('100%').css({
|
345
345
|
zIndex: 310,
|
346
346
|
position: 'absolute',
|
347
347
|
overflow: 'hidden'
|
348
348
|
}),
|
349
349
|
|
350
|
-
$hdl_holder = $('<div />')
|
351
|
-
.width('100%').height('100%').css('zIndex', 320),
|
350
|
+
$hdl_holder = $('<div />')
|
351
|
+
.width('100%').height('100%').css('zIndex', 320),
|
352
352
|
|
353
|
-
$sel = $('<div />')
|
353
|
+
$sel = $('<div />')
|
354
354
|
.css({
|
355
355
|
position: 'absolute',
|
356
356
|
zIndex: 600
|
357
357
|
}).dblclick(function(){
|
358
358
|
var c = Coords.getFixed();
|
359
359
|
options.onDblClick.call(api,c);
|
360
|
-
}).insertBefore($img).append($img_holder, $hdl_holder);
|
360
|
+
}).insertBefore($img).append($img_holder, $hdl_holder);
|
361
361
|
|
362
362
|
if (img_mode) {
|
363
363
|
|
@@ -393,7 +393,7 @@
|
|
393
393
|
// }}}
|
394
394
|
// }}}
|
395
395
|
// Internal Modules {{{
|
396
|
-
// Touch Module {{{
|
396
|
+
// Touch Module {{{
|
397
397
|
var Touch = (function () {
|
398
398
|
// Touch support detection function adapted (under MIT License)
|
399
399
|
// from code by Jeffrey Sambells - http://github.com/iamamused/
|
@@ -529,8 +529,8 @@
|
|
529
529
|
// This function could use some optimization I think...
|
530
530
|
var aspect = options.aspectRatio,
|
531
531
|
min_x = options.minSize[0] / xscale,
|
532
|
-
|
533
|
-
|
532
|
+
|
533
|
+
|
534
534
|
//min_y = options.minSize[1]/yscale,
|
535
535
|
max_x = options.maxSize[0] / xscale,
|
536
536
|
max_y = options.maxSize[1] / yscale,
|
@@ -1049,7 +1049,7 @@
|
|
1049
1049
|
{
|
1050
1050
|
seehandles = false;
|
1051
1051
|
$hdl_holder.hide();
|
1052
|
-
}
|
1052
|
+
}
|
1053
1053
|
//}}}
|
1054
1054
|
function animMode(v) //{{{
|
1055
1055
|
{
|
@@ -1058,13 +1058,13 @@
|
|
1058
1058
|
} else {
|
1059
1059
|
enableHandles();
|
1060
1060
|
}
|
1061
|
-
}
|
1061
|
+
}
|
1062
1062
|
//}}}
|
1063
1063
|
function done() //{{{
|
1064
1064
|
{
|
1065
1065
|
animMode(false);
|
1066
1066
|
refresh();
|
1067
|
-
}
|
1067
|
+
}
|
1068
1068
|
//}}}
|
1069
1069
|
// Insert draggable elements {{{
|
1070
1070
|
// Insert border divs for outline
|
@@ -1120,7 +1120,7 @@
|
|
1120
1120
|
done: done
|
1121
1121
|
};
|
1122
1122
|
}());
|
1123
|
-
|
1123
|
+
|
1124
1124
|
//}}}
|
1125
1125
|
// Tracker Module {{{
|
1126
1126
|
var Tracker = (function () {
|
@@ -1143,7 +1143,7 @@
|
|
1143
1143
|
.bind('mousemove.jcrop',trackMove)
|
1144
1144
|
.bind('mouseup.jcrop',trackUp);
|
1145
1145
|
}
|
1146
|
-
}
|
1146
|
+
}
|
1147
1147
|
//}}}
|
1148
1148
|
function toBack() //{{{
|
1149
1149
|
{
|
@@ -1151,13 +1151,13 @@
|
|
1151
1151
|
zIndex: 290
|
1152
1152
|
});
|
1153
1153
|
$(document).unbind('.jcrop');
|
1154
|
-
}
|
1154
|
+
}
|
1155
1155
|
//}}}
|
1156
1156
|
function trackMove(e) //{{{
|
1157
1157
|
{
|
1158
1158
|
onMove(mouseAbs(e));
|
1159
1159
|
return false;
|
1160
|
-
}
|
1160
|
+
}
|
1161
1161
|
//}}}
|
1162
1162
|
function trackUp(e) //{{{
|
1163
1163
|
{
|
@@ -1576,7 +1576,7 @@
|
|
1576
1576
|
}
|
1577
1577
|
};
|
1578
1578
|
|
1579
|
-
if (
|
1579
|
+
if (navigator.userAgent.match(/msie/i))
|
1580
1580
|
$div.bind('selectstart', function () { return false; });
|
1581
1581
|
|
1582
1582
|
$origimg.data('Jcrop', api);
|
@@ -2,8 +2,8 @@ class Spud::Admin::MediaController < Spud::Admin::ApplicationController
|
|
2
2
|
layout 'layouts/spud/admin/detail'
|
3
3
|
add_breadcrumb "Media", :spud_admin_media_path
|
4
4
|
belongs_to_spud_app :media
|
5
|
-
before_filter :load_media,:only => [:edit,:update,:show,:destroy,:set_private,:set_access]
|
6
|
-
|
5
|
+
before_filter :load_media,:only => [:edit,:update,:show,:destroy,:set_private,:set_access, :replace]
|
6
|
+
|
7
7
|
def index
|
8
8
|
@media = SpudMedia.order("created_at DESC").paginate :page => params[:page]
|
9
9
|
respond_with @media
|
@@ -22,7 +22,7 @@ class Spud::Admin::MediaController < Spud::Admin::ApplicationController
|
|
22
22
|
@media = SpudMedia.new(params[:spud_media])
|
23
23
|
location = spud_admin_media_path
|
24
24
|
if @media.save
|
25
|
-
flash[:notice] = "File uploaded successfully"
|
25
|
+
flash[:notice] = "File uploaded successfully"
|
26
26
|
if @media.is_image?
|
27
27
|
location = edit_spud_admin_medium_path(@media.id)
|
28
28
|
end
|
@@ -43,6 +43,9 @@ class Spud::Admin::MediaController < Spud::Admin::ApplicationController
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
+
def replace
|
47
|
+
end
|
48
|
+
|
46
49
|
def update
|
47
50
|
if @media.update_attributes(params[:spud_media])
|
48
51
|
@media.attachment.reprocess!
|
@@ -68,6 +71,6 @@ private
|
|
68
71
|
flash[:error] = "Media Asset not found!"
|
69
72
|
redirect_to spud_admin_media_url() and return
|
70
73
|
end
|
71
|
-
|
74
|
+
|
72
75
|
end
|
73
76
|
end
|
data/app/models/spud_media.rb
CHANGED
@@ -3,10 +3,10 @@ class SpudMedia < ActiveRecord::Base
|
|
3
3
|
has_attached_file :attachment,
|
4
4
|
:storage => Spud::Media.paperclip_storage,
|
5
5
|
:s3_credentials => Spud::Media.s3_credentials,
|
6
|
-
:s3_permissions => lambda { |attachment, style|
|
7
|
-
attachment.instance.is_protected ? 'private' : 'public-read'
|
6
|
+
:s3_permissions => lambda { |attachment, style|
|
7
|
+
attachment.instance.is_protected ? 'private' : 'public-read'
|
8
8
|
},
|
9
|
-
:path => Spud::Media.paperclip_storage == :s3 ? Spud::Media.storage_path : lambda { |attachment|
|
9
|
+
:path => Spud::Media.paperclip_storage == :s3 ? Spud::Media.storage_path : lambda { |attachment|
|
10
10
|
attachment.instance.is_protected ? Spud::Media.storage_path_protected : Spud::Media.storage_path
|
11
11
|
},
|
12
12
|
:url => Spud::Media.storage_url,
|
@@ -17,10 +17,15 @@ class SpudMedia < ActiveRecord::Base
|
|
17
17
|
validates_numericality_of :crop_x, :crop_y, :crop_w, :crop_h, :crop_s, :allow_nil => true
|
18
18
|
|
19
19
|
before_create :rename_file
|
20
|
+
before_update :rename_file
|
20
21
|
#after_create :validate_permissions
|
21
22
|
before_update :validate_permissions
|
22
23
|
|
23
24
|
def rename_file
|
25
|
+
if self.attachment_file_name_changed? && self.attachment_file_name_was.nil? == false
|
26
|
+
attachment.instance_write :file_name, self.attachment_file_name_was
|
27
|
+
return
|
28
|
+
end
|
24
29
|
# remove periods and other unsafe characters from file name to make routing easier
|
25
30
|
extension = File.extname(attachment_file_name)
|
26
31
|
filename = attachment_file_name.chomp(extension).parameterize
|
@@ -34,35 +39,35 @@ class SpudMedia < ActiveRecord::Base
|
|
34
39
|
|
35
40
|
elsif self.attachment_content_type.blank?
|
36
41
|
return "spud/admin/files_thumbs/dat_thumb.png"
|
37
|
-
|
42
|
+
|
38
43
|
elsif self.attachment_content_type.match(/jpeg|jpg/)
|
39
44
|
return "spud/admin/files_thumbs/jpg_thumb.png"
|
40
|
-
|
45
|
+
|
41
46
|
elsif self.attachment_content_type.match(/png/)
|
42
47
|
return "spud/admin/files_thumbs/png_thumb.png"
|
43
|
-
|
48
|
+
|
44
49
|
elsif self.attachment_content_type.match(/zip|tar|tar\.gz|gz/)
|
45
50
|
return "spud/admin/files_thumbs/zip_thumb.png"
|
46
|
-
|
51
|
+
|
47
52
|
elsif self.attachment_content_type.match(/xls|xlsx/)
|
48
53
|
return "spud/admin/files_thumbs/xls_thumb.png"
|
49
|
-
|
54
|
+
|
50
55
|
elsif self.attachment_content_type.match(/doc|docx/)
|
51
56
|
return "spud/admin/files_thumbs/doc_thumb.png"
|
52
|
-
|
57
|
+
|
53
58
|
elsif self.attachment_content_type.match(/ppt|pptx/)
|
54
59
|
return "spud/admin/files_thumbs/ppt_thumb.png"
|
55
|
-
|
60
|
+
|
56
61
|
elsif self.attachment_content_type.match(/txt|text/)
|
57
62
|
return "spud/admin/files_thumbs/txt_thumb.png"
|
58
|
-
|
63
|
+
|
59
64
|
elsif self.attachment_content_type.match(/pdf|ps/)
|
60
65
|
return "spud/admin/files_thumbs/pdf_thumb.png"
|
61
|
-
|
66
|
+
|
62
67
|
elsif self.attachment_content_type.match(/mp3|wav|aac/)
|
63
68
|
return "spud/admin/files_thumbs/mp3_thumb.png"
|
64
69
|
end
|
65
|
-
|
70
|
+
|
66
71
|
return "spud/admin/files_thumbs/dat_thumb.png"
|
67
72
|
end
|
68
73
|
|
@@ -130,7 +135,7 @@ private
|
|
130
135
|
new_path = Paperclip::Interpolations.interpolate(Spud::Media.config.storage_path_protected, attachment, 'original')
|
131
136
|
else
|
132
137
|
old_path = Paperclip::Interpolations.interpolate(Spud::Media.config.storage_path_protected, attachment, 'original')
|
133
|
-
new_path = Paperclip::Interpolations.interpolate(Spud::Media.config.storage_path, attachment, 'original')
|
138
|
+
new_path = Paperclip::Interpolations.interpolate(Spud::Media.config.storage_path, attachment, 'original')
|
134
139
|
end
|
135
140
|
new_base_dir = File.dirname(File.dirname(new_path))
|
136
141
|
old_base_dir= File.dirname(File.dirname(old_path))
|
@@ -5,14 +5,14 @@
|
|
5
5
|
<div class="page_list">
|
6
6
|
<%@media.each do |media|%>
|
7
7
|
<div class="page_row">
|
8
|
-
|
8
|
+
|
9
9
|
<span class="row_meta">
|
10
10
|
<% if media.is_protected && (media.is_image? || media.is_pdf?) %>
|
11
11
|
<img src="<%= media.image_from_type %>" class="size-50-thumb" />
|
12
12
|
<% else %>
|
13
13
|
<%= image_tag(media.image_from_type, :class => "size-50-thumb") %>
|
14
14
|
<% end %>
|
15
|
-
<%= link_to media.attachment.url.split("/").last, media.attachment_url %>
|
15
|
+
<%= link_to media.attachment.url.split("/").last, media.attachment_url %>
|
16
16
|
</span>
|
17
17
|
|
18
18
|
<span class="edit_controls">
|
@@ -24,9 +24,10 @@
|
|
24
24
|
<% else %>
|
25
25
|
<%= link_to 'Public', set_access_spud_admin_medium_path(media.id, :protected => true), :method => :put, :class => 'btn' %>
|
26
26
|
<% end %>
|
27
|
+
<%= link_to "Replace", replace_spud_admin_medium_path(:id => media.id), :class => "btn"%>
|
27
28
|
<%=link_to "Remove", spud_admin_medium_path(:id => media.id),:method => :delete,:class => 'btn btn-danger',:confirm => "Are you sure you want to remove this file?"%>
|
28
29
|
</span>
|
29
|
-
|
30
|
+
|
30
31
|
<br style="clear:both;"/>
|
31
32
|
</div>
|
32
33
|
<%end%>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
<%=form_for @media,:url => spud_admin_medium_path(:id => @media.id),:html=>{:class=>"form-horizontal", :multipart => true, :method => :put} do |f|%>
|
5
|
+
<fieldset>
|
6
|
+
<legend>Replace File</legend>
|
7
|
+
|
8
|
+
<div class="control-group">
|
9
|
+
<%=f.label :attachment, "Choose File",:class =>"control-label"%>
|
10
|
+
<div class="controls">
|
11
|
+
<%=f.file_field :attachment%>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<div class="control-group">
|
16
|
+
<%= f.label :is_protected, 'Protected File', :class => 'control-label' %>
|
17
|
+
<div class="controls">
|
18
|
+
<%= f.check_box :is_protected %>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
<div class="form-actions">
|
23
|
+
<%=f.submit "Upload", :class=>"btn btn-primary","data-loading-text"=>"Uploading..."%> or <%=link_to "cancel",spud_admin_media_path,:class => "btn"%>
|
24
|
+
</div>
|
25
|
+
<%end%>
|
26
|
+
|
data/config/routes.rb
CHANGED
@@ -2,12 +2,13 @@ Rails.application.routes.draw do
|
|
2
2
|
namespace :spud do
|
3
3
|
namespace :admin do
|
4
4
|
resources :media do
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
put 'set_access', :on => :member
|
6
|
+
get 'replace', :on => :member
|
7
|
+
end
|
8
|
+
resources :media_picker, :only => [:index, :create]
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
11
12
|
#get '/media/protected/:id/:style/:filename' => 'ProtectedMedia#show', :as => 'protected_media'
|
12
13
|
get Spud::Media.config.storage_url => 'ProtectedMedia#show', :as => 'protected_media'
|
13
|
-
end
|
14
|
+
end
|
data/lib/spud_media/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spud_media
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.6
|
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:
|
12
|
+
date: 2013-07-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -238,6 +238,7 @@ files:
|
|
238
238
|
- app/views/spud/admin/media/edit.html.erb
|
239
239
|
- app/views/spud/admin/media/index.html.erb
|
240
240
|
- app/views/spud/admin/media/new.html.erb
|
241
|
+
- app/views/spud/admin/media/replace.html.erb
|
241
242
|
- app/views/spud/admin/media_picker/_media.html.erb
|
242
243
|
- app/views/spud/admin/media_picker/create.html.erb
|
243
244
|
- app/views/spud/admin/media_picker/create.js.erb
|
@@ -269,7 +270,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
269
270
|
version: '0'
|
270
271
|
segments:
|
271
272
|
- 0
|
272
|
-
hash:
|
273
|
+
hash: 2768522942128927229
|
273
274
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
274
275
|
none: false
|
275
276
|
requirements:
|
@@ -278,10 +279,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
278
279
|
version: '0'
|
279
280
|
segments:
|
280
281
|
- 0
|
281
|
-
hash:
|
282
|
+
hash: 2768522942128927229
|
282
283
|
requirements: []
|
283
284
|
rubyforge_project:
|
284
|
-
rubygems_version: 1.8.
|
285
|
+
rubygems_version: 1.8.25
|
285
286
|
signing_key:
|
286
287
|
specification_version: 3
|
287
288
|
summary: Spud File upload/management module
|