Rails upload large file - ruby-on-rails

I ran into some difficulties while uploading larges files using rails 5.
File is uploaded using ajax and simply grab inside a rails controller.
Used server is : puma.
The file transfer is speed (followed by ajax xhr progress, in a local network (Gigabit)).
But the save of the file /tmp/RackMultipart* took a long time.
I suppose that the file is loaded in memory by Rack, that process and save it in /tmp/. After that, the controller is proceed.
The code work perfectly for small files like images.
But for large file > 100 Mo the completed execution take around 1 minute ...
My code :
The upload area :
views/_attachments.html.erb
<div class="card">
<div class="card-header">
Fichiers
</div>
<div class="card-block">
<span id="attachment-area-message"></span>
<div id="attachment-area">
Déposez vos fichiers ici
</div>
<!-- Area for progress bar -->
<div id="progress-wrapper"></div>
<script>
var attachment_token = '<%= form_authenticity_token %>';
var attachment_model_name = '<%= fileable.class.name %>';
var attachment_model_id = '<%= fileable.id %>';
</script>
</div>
<div class="card-block">
<div class="attachfiles-wrapper">
<div id="attachfiles">
<% fileable.attachments.includes('user').order(created_at: :asc).each do |attachment| %>
<%= render partial: 'app/attachments/attachment', locals: { attachment: attachment } %>
<% end %>
</div>
</div>
</div>
</div>
JS file which launch the upload :
$(document).on('turbolinks:load', function() {
new Clipboard('.btn-clipboard');
var upload_mime = [
'application/zip',
// Image
'image/png',
'image/jpeg',
'image/gif',
'image/tiff',
'image/svg+xml',
];
var upload_maxSize = 3000000000;
var server_url = '/app/attachments/upload.js'; // Route for upload file, .js for the js call back
var element = $("#attachment-area");
// EVENTS
// ----------------------------------------------------------------------------
element.on('dragover', function(e) {
e.preventDefault();
e.stopPropagation();
});
element.on('dragenter', function(e) {
element.addClass('active');
e.preventDefault();
e.stopPropagation();
});
element.on('dragleave', function(e) {
element.removeClass('active');
e.preventDefault();
e.stopPropagation();
});
element.on('drop', function(e) {
element.removeClass('active');
e.preventDefault();
e.stopPropagation();
if (e.originalEvent.dataTransfer){
if (e.originalEvent.dataTransfer.files.length > 0) {
console.log(e.originalEvent.dataTransfer.files);
upload(e.originalEvent.dataTransfer.files);
}
}
return false;
});
// UPLOADS
// ----------------------------------------------------------------------------
var upload = function(files) {
// Send each file
$.each(files, function(key, file) {
// TEST THE FILE
// ----------------------
var FileValidate = true;
// Size
if(file.size > upload_maxSize) {
$('#attachment-area-message').append(file.name + " : Fichier trop lourd (3 Go maximum) : " + file.size);
FileValidate = false;
}
// Mime type
if( upload_mime.indexOf(file.type) == -1 ) {
$('#attachment-area-message').append( file.name + " : Type de fichier non authorisé : " + file.type);
$('#attachment-area-message').append( "<br>Essayez de zipper le fichier");
FileValidate = false;
}
if(!FileValidate) return true; // Continue to next iteration
// SEND FILE
// ----------------------
console.log(file);
var formData = new FormData();
formData.append('attachment[file]', file );
formData.append("attachment[model_name]", attachment_model_name);
formData.append("attachment[model_id]", attachment_model_id);
console.log(formData);
// Progress Bar Name
var progress_name = file.name.replace(/[^a-zA-Z]/g,'-').toLowerCase();
// Send the request :)
$.ajax({
url: server_url,
data: formData,
type: 'POST',
beforeSend: function(request) {
request.setRequestHeader('X-CSRF-Token', attachment_token);
console.log('BEFORE SEND');
},
contentType: false, // NEEDED, DON'T OMIT THIS (requires jQuery 1.6+)
processData: false, // NEEDED, DON'T OMIT THIS
xhr: function() {
// create an XMLHttpRequest
var xhr = new XMLHttpRequest();
console.log('xhr');
xhr.upload.onprogress = function (e) {
console.log('xhr progress');
if (e.lengthComputable) {
var percente = Math.round( ( e.loaded * 100 ) / e.total );
$('.' + progress_name + ' .progress-bar').width(percente + "%");
}
};
xhr.onloadstart = function (e) {
console.log('xhr onloadstart');
$('#progress-wrapper').append('<div class="' + progress_name + '" style="margin-top:5px;">'
+ '<span class="description">' + file.name + '</span>'
+ '<div class="progress" id="file-upload-bar">'
+ '<div class="progress-bar bg-info" role="progressbar" style="width:0%; height:10px;" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"></div>'
+ '</div></div>');
};
xhr.onload = function (e) {
console.log('xhr onload');
if (xhr.status === 200) eval(xhr.responseText); // Grab the return of rails controller (format.js)
};
xhr.onloadend = function (e) {
console.log('xhr onloadend');
$('.' + progress_name).remove();
};
return xhr;
}
});
});
};
});
And the controller :
(Attachment model is polymorphic base on fileable).
class App::AttachmentsController < AppController
before_action :find_fileable
def upload
# Get the File
uploaded_io = attach_params[:file]
logger.debug '---------'
logger.debug params.inspect
logger.debug '---------'
# Define file destination
dest = Rails.root.join('public', 'uploads', 'attachments', attach_params[:model_name], attach_params[:model_id], uploaded_io.original_filename)
file_name = uploaded_io.original_filename
file_basename = File.basename(uploaded_io.original_filename, '.*')
file_extname = File.extname(uploaded_io.original_filename)
# Make dir
dir = File.dirname( dest )
FileUtils.mkdir_p(dir) unless File.directory?(dir)
# Test if file exist (and update version if needed)
if File.exist?(dest)
version = 0
loop do
version += 1
file_name = file_basename + '-' + version.to_s + file_extname
dest = Rails.root.join('public', 'uploads', 'attachments', attach_params[:model_name], attach_params[:model_id], file_name )
break if !File.exist?(dest)
end
end
# Copy file to dest
#FileUtils.cp uploaded_io.path, dest
File.open( dest, 'wb') do |file|
file.write(uploaded_io.read)
end
# Save in database
#attach = #fileable.attachments.new
#attach.user_id = #current_user.id
#attach.name = file_name
#attach.size = uploaded_io.size
#attach.mime = uploaded_io.content_type
#attach.key = Digest::SHA1.hexdigest([Time.now, rand].join)
respond_to do |format|
if #attach.save
flash[:success] = "Fichier ajouté"
format.js # upload.js callback add new file to the list of files
else
flash[:warning] = "Fichier non enregistré :("
end
end
end
private
def attach_params
params.require( :attachment ).permit( :model_id, :model_name, :file )
end
def find_fileable
#fileable = Task.find_by_id( attach_params[:model_id] ) if attach_params[:model_name] == 'Task'
end
end
I have tested different file management solutions : CarrierWave, Shrine, ...
Unfortunately the problem is still there. Always the rack save in front.
Any help or any idea ? I want to eat this "Rack"
Thanks,
Seb.

The issue is that Rack multipart parser writes to disk (that's the fast part), but that its implementation is slow and expensive (see rack/rack#1075). However, the Rack master has huge performance improvements to the multipart parser, so using master should solve your problem.
gem "rack", github: "rack/rack"
We can verify this by running the following script:
require "rack"
require "rack/test_app" # https://github.com/kwatch/rack-test_app
require "benchmark"
app = -> (env) do
puts Benchmark.realtime { Rack::Request.new(env).params } # trigger multipart parsing
[200, {}, []]
end
File.write("file.txt", "a" * 100*1024*1024)
test_app = Rack::TestApp.wrap(app)
test_app.post("/", multipart: {file: File.open("file.txt")})
Rack 2.0.3:
$ ruby multipart.rb
62.617582999984734
Rack master:
$ ruby multipart.rb
0.3564810000243597

I have test with chunk method. Create part of 1 Mo of my file and send them in binary. It's better but not the perfection.
With this method rails do not create a MultiRack* file in tmp, but it use memory in both sides, server and client.
The javascript file :
$(document).on('turbolinks:load', function() {
new Clipboard('.btn-clipboard');
var upload_url = '/app/attachments/upload'; // Route for upload file, .js for the js call back
var upload_part_url = '/app/attachments/upload/part/';
var upload_mime = [
'application/zip',
// Vidéo
'video/mp4',
'video/mpeg',
'video/x-flv',
// Audio
'audio/mpeg',
// Image
'image/png',
'image/jpeg',
'image/gif',
'image/tiff',
'image/svg+xml',
// Text
'text/csv',
'text/html',
// Application
'application/pdf',
'application/msword',
'application/excel',
'application/mspowerpoint',
// Adobe
'application/vnd.adobe.indesign',
'application/x-indesign',
'application/indesign',
'image/vnd.adobe.photoshop',
'application/x-photoshop',
'application/photoshop',
'application/psd',
'image/psd',
'application/illustrator',
'application/postscript'
];
var upload_maxSize = 3000000000;
// EVENTS on DROP AREA
// ----------------------------------------------------------------------------
var element = $("#attachment-area"); // Drop area
element.on('dragover', function(e) {
e.preventDefault();
e.stopPropagation();
});
element.on('dragenter', function(e) {
element.addClass('active');
e.preventDefault();
e.stopPropagation();
});
element.on('dragleave', function(e) {
element.removeClass('active');
e.preventDefault();
e.stopPropagation();
});
element.on('drop', function(e) {
element.removeClass('active');
e.preventDefault();
e.stopPropagation();
if (e.originalEvent.dataTransfer){
if (e.originalEvent.dataTransfer.files.length > 0) {
// We upload the files
$.each(e.originalEvent.dataTransfer.files, function(key, file) {
// Test the file
var FileValidate = true;
// Size
if(file.size > upload_maxSize) {
$('#attachment-area-message').append(file.name + " : Fichier trop lourd (3 Go maximum) : " + file.size);
FileValidate = false;
}
// Mime type
if( upload_mime.indexOf(file.type) == -1 ) {
$('#attachment-area-message').append( file.name + " : Type de fichier non authorisé : " + file.type);
$('#attachment-area-message').append( "<br>Essayez de zipper le fichier");
FileValidate = false;
}
// Begin the upload
if(FileValidate) upload(file);
});
}
}
return false;
});
// UPLOAD
// ----------------------------------------------------------------------------
var upload = function(file) {
console.log(file);
var formData = new FormData();
formData.append("attachment[model_name]", attachment_model_name);
formData.append("attachment[model_id]", attachment_model_id);
formData.append('attachment[file_name]', file.name );
formData.append('attachment[file_size]', file.size );
formData.append('attachment[file_mime]', file.type );
// Progress Bar Name
// var progress_name = file.name.replace(/[^a-zA-Z]/g,'-').toLowerCase();
// Send the file infos
var req = new XMLHttpRequest();
// Request events
req.upload.onprogress = function (e) {
console.log('xhr progress');
};
req.onloadstart = function (e) {
console.log('xhr onloadstart');
};
// Error
req.onerror = function (e) {
}
// Success
req.onload = function (e) {
console.log('xhr onload');
if (req.status === 200) {
attach = JSON.parse(req.responseText);
if(typeof attach.id !== 'undefined') uploadFileData(file, attach.id ); // Send the data
}
};
// Complete
req.onloadend = function (e) {
console.log('xhr onloadend');
};
// Send the file infos Request
req.open("POST", upload_url);
req.setRequestHeader('X-CSRF-Token', attachment_token);
req.send(formData);
};
// UPLOAD FILE CHUNKS
// ----------------------------------------------------------------------------
var uploadFileData = function(file, id) {
var reader = new FileReader();
// Process after the file is read
reader.onload = function (e) {
var chunkSize = 1*1024*1024;
var buffer = this.result;
var fileSize = buffer.byteLength;
var segments = Math.ceil(fileSize / chunkSize);
var count = 0;
var fileId = id;
// Send part
(function sendPart() {
var segSize = Math.min(chunkSize, fileSize - count * chunkSize);
var returnFormat = segSize < chunkSize ? '.js' : '.json' ;
if (segSize > 0) {
var chunk = new Uint8Array(buffer, count++ * chunkSize, segSize); // get a chunk
// update progress bar
var req = new XMLHttpRequest();
// Request events
req.upload.onprogress = function (e) {
console.log('part progress : ' + count );
};
req.onloadstart = function (e) {
console.log('part onloadstart : ' + count );
};
// Error
req.onerror = function (e) {
}
// Success
req.onload = function (e) {
console.log('part next : ' + count );
sendPart(); // Success -> Next part
};
// Send the file part data
req.open("POST", upload_part_url + fileId + returnFormat);
req.setRequestHeader('X-CSRF-Token', attachment_token);
req.setRequestHeader('Content-Type', 'application/octet-stream');
req.send(chunk);
}
else {
// hide progress bar
console.log("part Done : " + count);
}
})()
};
// Read the file
reader.readAsArrayBuffer(file);
reader.onprogress = function(e) {
// loaded += e.loaded;
// progress.value = (loaded/total) * 100;
};
}
});
The controller :
class App::AttachmentsController < AppController
before_action :find_fileable, only: [:upload]
def upload
# Define file destination
model_name = attach_params[:model_name]
model_id = attach_params[:model_id]
file_name = attach_params[:file_name]
file_basename = File.basename(file_name, '.*')
file_extname = File.extname(file_name)
dest = Rails.root.join('public', 'uploads', 'attachments', model_name, model_id, file_name)
# Make dir
dir = File.dirname( dest )
FileUtils.mkdir_p(dir) unless File.directory?(dir)
# Test if file exist (and update version in name if needed)
if File.exist?(dest)
version = 0
loop do
version += 1
file_name = file_basename + '-' + version.to_s + file_extname
dest = Rails.root.join('public', 'uploads', 'attachments', model_name, model_id, file_name )
break if !File.exist?(dest)
end
end
# Save in database
#a = #fileable.attachments.new
#a.user_id = #current_user.id
#a.name = file_name
#a.size = attach_params[:file_size]
#a.mime = attach_params[:file_mime]
#a.key = Digest::SHA1.hexdigest( [Time.now, rand].join )
#a.completed = false
#a.save
logger.debug '----'
logger.debug #a.to_json
logger.debug '----'
render status: 200, json: #a.to_json
end
def upload_part
#attach = Attachment.find(params[:id])
logger.debug '----'
logger.debug #attach.inspect
logger.debug '----'
dest = #attach.path
# Copy file to dest
File.open( dest, 'ab') do |file|
file.write(request.raw_post)
end
logger.debug '----'
logger.debug File.size(dest)
logger.debug #attach.size
logger.debug '----'
respond_to do |format|
format.js
format.json { render status: 200, json: { "status": "yop"} }
end
end
private
def attach_params
params.require( :attachment ).permit( :model_name, :model_id, :file_name, :file_size, :file_mime )
end
def find_fileable
#fileable = Task.find_by_id( attach_params[:model_id] ) if attach_params[:model_name] == 'Task'
end
end
See you in next episode. I keep searching ...

Related

I have a 404 error in an Ajax call on Ruby on rails

I'm pretty new to ajax (and web development in general) and I'm trying to implement this code directly on my view:
<% content_for :after_js do %>
<script type="text/javascript">
//1st dropdown selection
const dropdownOne = document.querySelector('#order_credits_package_id');
//Listening to dropdown change
dropdownOne.addEventListener('change', (event) => {
const selectedPackage = event.target.value
console.log(selectedPackage)
// document.getElementById("order_amount_centavos").value = event.target.value
// Params for AJAX call
const url = "/dynamic_dropdown_method";
const data = {'selection': selectedPackage }
// Storage of AJAX call answer (NB: can be refactored to avoid 2 calls)
// the ['id'] must match the one defined in your controller
const level_1_selected = ajaxCall(url, data)['dropdown_1_selected'];
const level_2_array = ajaxCall(url, data)['dropdown_2_array'];
// Identification of 2nd dropdown
const dropdownTwo = document.querySelector('#order_amount_centavos');
// Delete and replace options in 2nd dropdown
removeOptions(dropdownTwo);
addOptions(level_2_array, dropdownTwo)
});
// AJAX call
function ajaxCall(url,data) {
var result = "";
console.log("call Ajax")
console.log(url)
console.log(data)
$.ajax({
url: url,
async: false,
dataType: 'json',
type: 'POST',
data: data,
success: function (response) {
console.log("ajax Call")
console.log(response)
result = response;
}
});
return result;
}
// Delete existing select options
function removeOptions(selectElement) {
console.log("fonction remove")
var i, L = selectElement.options.length - 1;
for(i = L; i >= 0; i--) {
selectElement.remove(i);
}
}
// Add select option
function addOptions(optionsArray, dropdown) {
optionsArray.forEach(function (item) {
const new_option = document.createElement('option');
new_option.value = item;
new_option.innerHTML = item;
dropdown.appendChild(new_option);
});
}
</script>
<% end %>
I then added this method to the controller related to this view:
def dynamic_dropdown_method
#selection = params[:credits_package_id]
#array_dropdown_2 = CreditsPackage.find(#selection).price_centavos
return render json: {level_1_selected: #selection, level_2_array: #array_dropdown_2}.to_json
end
And the following route:
post "/dynamic_dropdown_method", to: "orders#dynamic_dropdown_method", as: :dynamic_dropdown_method
But when I try to run my code, I get the following error message in the console:
POST http://localhost:3000/dynamic_dropdown_method 404 (Not Found)
Does anyone have any idea how to fix it?
Thank you very much!

Post not working React + Rails

When I try to make a post I'm getting a 400 (Bad Request) Error.
I'm trying to post a new Player into a Team.
My controller
def create
#teams = Team.find(params[:team_id])
#players = #teams.players.new(player_params)
render json: #players
end
private
def player_params
params.require(:player).permit(:name, :photo, :nationality)
end
The function
_handleSubmit = async (e) => {
e.preventDefault();
const teamId = this.props.match.params.id;
const payload = this.state.players;
console.log(payload);
try {
const res = await axios.post(`/api/teams/${teamId}/players`, payload);
} catch (err) {
console.log(err);
}
};
_handleChange = (e) => {
const newState = { ...this.state.players };
newState[e.target.name] = e.target.value;
this.setState({ players: newState });
};
You need to strong compare what the body you send, and what the body you expect to receive on the server?
On backend you can send not only error code, and error message, or field identifier which error occurs.
And don't forget to set await statement if you will work with query result:
const res = await axios.post(`/api/teams/${teamId}/players`, payload);

Sending an image via a post request from an Ionic app to a rails endpoint

I am currently working on an Ionic mobile application which will eventually take photos, attach a location and send them inside a post request to a rails endpoint. After looking at this link and this link and countless others, I have been unable to find any solid information on implementing this particular feature.
I can upload photos through the browser using a html input form, which is then added to the database and is displayed on the app via a get request.
However at the moment when taking a photo on the phone and attempting to send it via a post request directly from the app, only the location information is being received, the image is not being correctly encoded.
Here is the jSON data that has been received, its returning "image_url":"/images/main/missing.png".
{ "id":6,"city":"Greater London",
"country":"United Kingdom","created_at":"2015-05-14T21:22:22.825Z",
"updated_at":"2015-05-14T21:22:22.825Z","image_file_name":null,
"image_content_type":null,"image_file_size":null,
"image_updated_at":null,"image_url":"/images/main/missing.png" }
Here is the code:
Angular factory making post request:
.factory('Posts', function($http) {
var o = { posts: [] };
o.getAll = function() {
return $http.get('http://localhost:8100/posts').success(function(data) {
angular.copy(data, o.posts);
});
};
o.addPost = function(post) {
return $http.post('https://shielded-hamlet-4665.herokuapp.com/posts', post);
};
return o;
})
Angular Controller taking photo:
.controller("CameraCtrl", function($scope, $cordovaCamera, $http, Posts) {
var id = 0;
var options = {
quality : 75,
destinationType : Camera.DestinationType.FILE_URI,
sourceType : 1,
allowEdit : true,
encodingType: 0,
targetWidth: 380,
targetHeight: 450,
popoverOptions: CameraPopoverOptions,
saveToPhotoAlbum: false
};
function getLocCoords(position) {
$scope.lat = position.coords.latitude;
$scope.lon = position.coords.longitude;
$http.get('http://maps.googleapis.com/maps/api/geocode/json?latlng=' + $scope.lat +',' + $scope.lon + '&sensor=true')
.success(function(data) {
var home = data.results[0].address_components;
for (var i = 0; i < home.length; i++) {
if(home[i].types.indexOf("administrative_area_level_2") > -1) {
$scope.city = home[i].long_name;
break;
};
};
for (var i = 0; i < home.length; i++) {
if(home[i].types.indexOf('country') > -1) {
$scope.country = home[i].long_name;
break;
};
};
})
};
$scope.takePicture = function() {
navigator.geolocation.getCurrentPosition(getLocCoords);
$cordovaCamera.getPicture(options).then(function(imageData) {
$scope.imgURI = imageData;
id ++;
var post = { id: id, country: $scope.country, city: $scope.city, image: $scope.imgURI, likes: 0, comments: [] }
Posts.addPost(post);
}, function(err) {
});
}
Post Controller from the Rails Database:
class PostsController < ApplicationController
skip_before_filter :verify_authenticity_token
def index
#posts = Post.all
render json: #posts, :callback => params['callback'], :content_type => 'application/javascript', :methods => [:image_url]
end
def new
#post = Post.new
end
def create
Post.create(post_params)
redirect_to '/posts'
end
def post_params
params.require(:post).permit(:city, :country, :image)
end
end
I have not done a great deal of work with the ionic framework so please forgive my ignorance. Any help would be greatly appreciated.
Managed to solve this using the cordovaFileTransfer.upload method.
The rails end point was also filtering params and looking for a post object, with a image string, and only an image string was being provided.
The following code is now working
Angular factory making post request:
.factory('Posts', function($http, $cordovaFileTransfer) {
var o = { posts: [] };
o.getAll = function() {
return $http.get('https://shielded-hamlet-4665.herokuapp.com/posts').success(function(data) {
angular.copy(data, o.posts);
});
};
o.addPost = function(post) {
var options = {
fileKey: "image",
fileName: "image.jpeg",
chunkedMode: false,
mimeType: "image/jpeg",
params: { city: post.city, country: post.country, lat: post.lat, lon: post.lon }
};
$cordovaFileTransfer.upload('http://shielded-hamlet-4665.herokuapp.com/posts', post.image, options)
.then(function(result){
console.log("Code = ok");
}, function(error){
console.log("Code = " + error);
}, function(progress){});
};
return o;
})
Angular Controller taking photo:
.controller("CameraCtrl", function($scope, $cordovaCamera, $http, Posts) {
post = {};
var options = {
quality : 75,
destinationType : Camera.DestinationType.FILE_URI,
sourceType : 1,
allowEdit : true,
encodingType: 0,
targetWidth: 380,
targetHeight: 450,
popoverOptions: CameraPopoverOptions,
saveToPhotoAlbum: false
};
function getLocCoords(position) {
post.lat = position.coords.latitude;
post.lon = position.coords.longitude;
$http.get('http://maps.googleapis.com/maps/api/geocode/json?latlng=' + post.lat +',' + post.lon + '&sensor=true')
.success(function(data) {
var home = data.results[0].address_components;
for (var i = 0; i < home.length; i++) {
if(home[i].types.indexOf("administrative_area_level_2") > -1) {
post.city = home[i].long_name;
break;
};
};
for (var i = 0; i < home.length; i++) {
if(home[i].types.indexOf('country') > -1) {
post.country = home[i].long_name;
break;
};
};
})
};
$scope.takePicture = function() {
navigator.geolocation.getCurrentPosition(getLocCoords);
$cordovaCamera.getPicture(options).then(function(imageData) {
post.image = imageData;
Posts.addPost(post);
}, function(err) {});
};
});
Post controller from rails database:
class PostsController < ApplicationController
skip_before_filter :verify_authenticity_token
def index
#posts = Post.all
render json: #posts, :callback => params['callback'], :content_type => 'application/javascript', :methods => [:image_url]
end
def new
#post = Post.new
end
def create
Post.create(post_params)
redirect_to '/posts'
end
def post_params
params.permit(:city, :country, :image, :lat, :lon)
end
end

Highcharts columns height in phantomjs generated pdf

I am trying to generate a pdf with phantomjs from a page that's using highcharts. This is the script I am using
var port, server, service
system = require('system');
var page = require('webpage').create();
page.onError = function (msg, trace) {
console.log(msg);
trace.forEach(function(item) {
console.log(' ', item.file, ':', item.line);
})
}
var fs = require('fs');
function loadFile(name){
if(fs.exists(name)){
console.log(name+ " File exist");
return fs.open(name,"r");
}else {
console.log("File do not exist");
}
}
if (system.args.length !== 2) {
console.log('Usage: serverkeepalive.js <portnumber>');
phantom.exit(1);
} else {
port = system.args[1];
console.log('port: ' + port);
server = require('webserver').create();
service = server.listen(port, { keepAlive: true }, function (request, response) {
console.log('Request at ' + new Date());
console.log(JSON.stringify(request, null, 4));
console.log('ProjectId:' + request.headers.projectId)
var projectReportPage = 'http://localhost:55073/' + request.headers.projectId;
console.log(projectReportPage);
console.log(JSON.stringify(request.cookies, null, 4));
phantom.cookiesEnabled = true;
phantom.addCookie({
'name': 'hello', /* required property */
'value': 'helloFromPhantomJS', /* required property */
'domain': 'localhost', /* required property */
'expires': (new Date()).getTime() + 3600 /* <- expires in 1 hour */
});
console.log(JSON.stringify(phantom.cookies, null, 4));
page.paperSize = {
format: 'A4',
orientation: 'portrait',
margin:'1cm' };
page.open(projectReportPage, function (status) {
if (status !== 'success') {
console.log('FAIL to load the address');
} else {
console.log('Page obtained');
var reportName = 'report_' + request.headers.projectId + '.pdf';
page.evaluate( function(){$('h1').css('color', 'red');});
page.render(reportName);
var body = fs.absolute(reportName);
//var body = page.renderBase64('pdf');
// var fi = loadFile(reportName);
// var body = fi.read();
// var rawBody = fs.read(reportName);
// console.log(rawBody);
// var body = base64Encode(rawBody);
console.log(body);
response.statusCode = 200;
response.headers = {
'Cache': 'no-cache',
'Content-Type': 'application/pdf',
'Connection': 'Keep-Alive',
'Content-Length': body.length
};
response.write(body);
response.close();
}
});
console.log('After page open handler');
});
if (service) {
console.log('Web server running on port ' + port);
} else {
console.log('Error: Could not create web server listening on port ' + port);
phantom.exit();
}
}
When viewing the page, the chart looks like this:
http://i.imgur.com/kVImodv.png
This is what it looks like on the pdf:
http://i.imgur.com/vGon6vb.png
Any insights on why this happens would be appreciated!
The problem is that PhantomJS immediately takes a snapshot when the page is loaded. However, the Highcharts graph has an animation which builds up the graph.
This means the graph is not completely built up when PhantomJS is taking the snapshot. There are two possible solutions.
1. Skip the Highcharts animations
Add this to your graph configuration object.
plotOptions: {
series: {
animation: false
}
}
Source
2. Add a delay when taking a snapshot
page.open(address, function (status) {
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
Source

Rails PaperClip drag and drop multiple files

I am using PaperClip with Rails to upload files and it works fine, however it would like to implment a drag and drop fileupload that allows for uploading of multiple files. and that each file shoudn't be more than a certain size.
Edit:
Here is what is what i have so far, i have created the javascript part. However i am lost as how to create the controller part:
var $dropArea = $(".drop-area");
$dropArea.bind({
dragover: function () {
$(this).addClass('hover');
return false;
},
dragend: function () {
$(this).removeClass('hover');
return false;
},
drop: function (e) {
e = e || window.event;
e.preventDefault();
e = e.originalEvent || e;
var files = (e.files || e.dataTransfer.files);
var $img = $('<img src="" class="uploadPic" title="" alt="" />');
for (var i = 0; i < files.length; i++) {
(function (i) {
var reader = new FileReader();
reader.onload = function (event) {
var newImg = $img.clone().attr({
src: event.target.result,
title: (files[i].name),
alt: (files[i].name)
});
$("body").append(newImg);
};
reader.readAsDataURL(files[i]);
var xhr = new XMLHttpRequest();
var fd = new FormData();
fd.append(files[i].name, files[i]);
xhr.open("POST", 'url', true);
xhr.send(fd);
})(i);
}
return false;
}
});
And this is the basic controller part:
def create
#image = Image.new(params[:image])
if #image.save
respond_to do |format|
format.html { redirect_to action: 'index', :notice => 'Image saved'}
format.js { redirect_to action: 'index', :notice => 'Image saved'}
format.xml { redirect_to action: 'index', :notice => 'Image saved'}
end
else
flash[:notice] = "Error, Please try again"
redirect_to action: 'new'
end
end
How can i do this?
Thanks
After some research, i found that it could be easily done like this:
var dropArea = document.getElementById("droparea"),
viewArea = document.getElementById("previewarea");
dropArea.addEventListener("drop", function(evt){
evt.preventDefault();
evt.stopPropagation();
previewFiles(evt.dataTransfer.files);
return false;
}, false);
function previewFiles(files){
for (i=0; i < files.length; i++){
if (typeof FileReader != "undefined"){
var img = document.createElement("img");
viewArea.appendChild(img);
var reader = new FileReader();
reader.onload = (function(theImg){
return function(evt){
theImg.src = evt.target.result;
}
}(img));
reader.readAsDataURL(files[i]);
}
}
uploadFiles(files);
}
function uploadFiles(files){
var fd = FormData();
var position = 0;
var max = files.length;
if (typeof fd != "undefined"){
function queue(){
if (max >= 1 && position <= max - 1){
fd.append("file", files[position]);
upload();
}
}
function upload(){
$.ajax({
url: '/boxes/hiThere',
data: fd,
processData: false,
contentType: false,
type: 'POST',
success: function(data){
position = position + 1;
queue();
}
});
}
queue();
}
}
dropArea.addEventListener("dragenter", function(evt){
if (evt){
this.className = "drag-enter";
}
endFn(evt);
}, false);
dropArea.addEventListener("dragleave", function(evt){
this.className = "";
endFn(evt);
}, false);
dropArea.addEventListener("dragover", function(evt){
endFn(evt);
evt.dataTransfer.dropEffect = 'move';
return false;
}, false);
function endFn(evt){
evt.preventDefault();
evt.stopPropagation();
}
and simply add the normal save in rails like this:
def hiThere
box = Box.new(params[:box])
box.user_id = current_user.id
box.file_path = params[:file]
if box.save!
set_flash "File saved sucessfully"
else
set_flash "Error, please try again"
end
respond_to do |format|
format.js { redirect_to action: :index, :notice => "Done" }
end
end

Resources