How to attach carrier wave version inline in email - ruby-on-rails-4

I have a tidbit model that has a carrierwave uploader. Im working on attaching an inline image in an email. If I do this:
#filename = #tidbit.image.instance_variable_get('#file').filename
attachments.inline[#filename] = #tidbit.image.read
I get an inline image in my email. However, it is the full size original version.
How would I inline attach a specific version (i.e.. :thumb) of the image?
If I do:
attachments.inline[#filename] = #tidbit.image(:thumb).read
I get an argument error 1 for 0.

Late reply, but it might help googlers and I had to do something similar. The following worked:
These are the versions present on my uploader class
version :thumb do
process :resize_to_fill => [122, 70]
end
version :medium do
process :resize_to_fill => [470, 470]
end
So to get the image in a certain version I just need to do, for example:
specific_version = uploader.image.medium.read
Where medium is the version I want.
In the original question's case you need to do:
attachments.inline[#filename] = #tidbit.image.thumb.read

Related

Ionic 2 / cordova-plugin-file File.writeFile() refuses to create binary file correctly (png image)

In summary
File.writeFile() creates a PNG file of 0 bytes when trying to write a Blob made from base64 data.
In my application, I am trying to create a file that consists of base64 data stored in the db. The rendered equivalent of the data is a small anti-aliased graph curve in black on a transparent background (never more that 300 x 320 pixels) that has previously been created and stored from a canvas element. I have independently verified that the stored base64 data is indeed correct by rendering it at one of various base64 encoders/decoders available online.
Output from "Ionic Info"
--------------------------------
Your system information:
Cordova CLI: 6.3.1
Gulp version: CLI version 3.9.1
Gulp local:
Ionic Framework Version: 2.0.0-rc.2
Ionic CLI Version: 2.1.1
Ionic App Lib Version: 2.1.1
Ionic App Scripts Version: 0.0.39
OS:
Node Version: v6.7.0
--------------------------------
The development platform is Windows 10, and I've been testing directly on a Samsung Galaxy S7 and S4 so far.
I know that the base64 data has to be converted into binary data (as a Blob) first, as File does not yet support writing base64 directly in to an image file. I found various techniques with which to do this, and the code which seems to suit my needs the most (and reflects a similar way I would have done it in java is illustrated below):
Main code from constructor:
this.platform.ready().then(() => {
this.graphDataService.getDataItem(this.job.id).then((data) =>{
console.log("getpic:");
let imgWithMeta = data.split(",")
// base64 data
let imgData = imgWithMeta[1].trim();
// content type
let imgType = imgWithMeta[0].trim().split(";")[0].split(":")[1];
console.log("imgData:",imgData);
console.log("imgMeta:",imgType);
console.log("aftergetpic:");
// this.fs is correctly set to cordova.file.externalDataDirectory
let folderpath = this.fs;
let filename = "dotd_test.png";
File.resolveLocalFilesystemUrl(this.fs).then( (dirEntry) => {
console.log("resolved dir with:", dirEntry);
this.savebase64AsImageFile(dirEntry.nativeURL,filename,imgData,imgType);
});
});
});
Helper to convert base64 to Blob:
// convert base64 to Blob
b64toBlob(b64Data, contentType, sliceSize) {
//console.log("data packet:",b64Data);
//console.log("content type:",contentType);
//console.log("slice size:",sliceSize);
let byteCharacters = atob(b64Data);
let byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
let slice = byteCharacters.slice(offset, offset + sliceSize);
let byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
let byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
console.log("size of bytearray before blobbing:", byteArrays.length);
console.log("blob content type:", contentType);
let blob = new Blob(byteArrays, {type: contentType});
// alternative way WITHOUT chunking the base64 data
// let blob = new Blob([atob(b64Data)], {type: contentType});
return blob;
}
save the image with File.writeFile()
// save the image with File.writeFile()
savebase64AsImageFile(folderpath,filename,content,contentType){
// Convert the base64 string in a Blob
let data:Blob = this.b64toBlob(content,contentType,512);
console.log("file location attempt is:",folderpath + filename);
File.writeFile(
folderpath,
filename,
data,
{replace: true}
).then(
_ => console.log("write complete")
).catch(
err => console.log("file create failed:",err);
);
}
I have tried dozens of different decoding techniques, but the effect is the same. However, if I hardcode simple text data into the writeFile() section, like so:
File.writeFile(
folderpath,
"test.txt",
"the quick brown fox jumps over the lazy dog",
{replace: true}
)
A text file IS created correctly in the expected location with the text string above in it.
However, I've noticed that whether the file is the 0 bytes PNG, or the working text file above, in both cases the ".then()" consequence clause of the File Promise never fires.
Additionally, I swapped the above method and used the Ionic 2 native Base64-To-Gallery library to create the images, which worked without a problem. However, having the images in the user's picture gallery or camera roll is not an option for me as I do not wish to risk a user's own pictures while marshalling / packing / transmitting / deleting the data-rendered images. The images should be created and managed as part of the app.
User marcus-robinson seems to have experienced a similar issue outlined here, but it was across all file types, and not just binary types as seems to be the case here. Also, the issue seems to have been closed:
https://github.com/driftyco/ionic/issues/5638
Anybody experiencing something similar, or possibly spot some error I might have caused? I've tried dozens of alternatives but none seem to work.
I had similar behaviour saving media files which worked perfectly on iOS. Nonetheless, I had the issue of 0 bytes file creation on some Android devices in release build (dev build works perfectly). After very long search, I followed the following solution
I moved the polyfills.js script tag to the top of the index.html in the ionic project before the cordova.js tag. This re-ordering somehow the issue is resolved.
So the order should look like:
<script src="build/polyfills.js"></script>
<script type="text/javascript" src="cordova.js"></script>
Works on ionic 3 and ionic 4.
The credits go to 1
I got that working with most of your code:
this.file.writeFile(this.file.cacheDirectory, "currentCached.jpeg", this.b64toBlob(src, "image/jpg", 512) ,{replace: true})
The only difference i had was:
let byteCharacters = atob(b64Data.replace(/^data:image\/(png|jpeg|jpg);base64,/, ''));
instead of your
let byteCharacters = atob(b64Data);
Note: I did not use other trimming etc. like those techniques you used in your constructor class.

Implement Thomas J Bradley's Signature Pad Ruby on Rails Carrierwave

I'm not very good at rails yet, and I'm trying to collect a user's signature at the end of a form. I've got the form showing up in my view just fine now, but I don't really know where to start to get it converted into an image.
On the documentation for Signature Pad it suggests using this code:
instructions = JSON.load(data).map { |h| "line #{h['mx']},#{h['my']} #{h['lx']},#{h['ly']}" } * ' '
system "convert -size 198x55 xc:transparent -stroke blue -draw '#{instructions}' signature.png"
but it doesn't have any documentation on where/how to use it.
Do I put this in my create function?
How would I get it working with the carrierwave uploader gem?
Thanks!
I was actually able to work through this. Here's what I did.
In the controller create method I added this code before #model.save
instructions = JSON.parse(params[:output]).map { |h| "line #{h['mx'].to_i},#{h['my'].to_i} #{h['lx'].to_i},#{h['ly'].to_i}" } * ' '
tempfile = Tempfile.new(["signature", '.png'])
Open3.popen3("convert -size 600x100 xc:transparent -stroke blue -draw #- #{tempfile.path}") do |input, output, error|
input.puts instructions
end
#yourmodel.signature = tempfile
For this to work of course you have to have a Carrierwave gem installed, then set up an uploader like this.
In Terminal:
rails generate uploader Signature
In the model you're uploading to:
mount_uploader :signature, SignatureUploader
Hope that helps someone with the same issue.

How can I change my regex to get a different set of images with WWW::Mechanize?

I am making a web scraper for a website where I have to download images. I am currently using WWW::Mechanize and doing:
my #images=$mech->find_all_images(url_regex => qr/smallThumb/i);
which gives me all the images that have smallThumb in the URL.
How can I change smallThumb to zoom while retaining the previous links that have smallThumb?
You can do this:
my #smallthumbs = $mech->find_all_images(url_regex => qr/smallThumb/i);
my #zooms = $mech->find_all_images(url_regex => qr/zoom/i);
my #allimages = (#smallthumbs, #zooms);
The risk here is that you could have a URL that fits in both categories and get a dupe.
You can also go monkeying with the regex.
my #smallthumbs_or_zooms = $mech->find_all_images( url_regex => qr/smallThumb|zoom/i );

Rails 4 and paperclip - Stop the :original style file upload to copy it from an S3 remote directory

I use Paperclip 4.0.2 and in my app to upload pictures.
So my Document model has an attached_file called attachment
The attachment has few styles, say :medium, :thumb, :facebook
In my model, I stop the styles processing, and I extracted it inside a background job.
class Document < ActiveRecord::Base
# stop paperclip styles generation
before_post_process
false
end
But the :original style file is still uploaded!
I would like to know if it's possible to stop this behavior and copy the file inside the :original/filename.jpg from a remote directory
My goal being to use a file that has been uploaded in a S3 /temp/ directory with jQuery File upload, and copy it to the directory where Paperclip needs it to generate the others styles.
Thank you in advance for your help!
New Answer:
paperclip attachments get uploaded in the flush_writes method which, for your purposes, is part of the Paperclip::Storage::S3 module. The line which is responsible for the uploading is:
s3_object(style).write(file, write_options)
So, by means of monkey_patch, you can change this to something like:
s3_object(style).write(file, write_options) unless style.to_s == "original" and #queued_for_write[:your_processed_style].present?
EDIT: this would be accomplished by creating the following file: config/initializers/decorators/paperclip.rb
Paperclip::Storage::S3.class_eval do
def flush_writes #:nodoc:
#queued_for_write.each do |style, file|
retries = 0
begin
log("saving #{path(style)}")
acl = #s3_permissions[style] || #s3_permissions[:default]
acl = acl.call(self, style) if acl.respond_to?(:call)
write_options = {
:content_type => file.content_type,
:acl => acl
}
# add storage class for this style if defined
storage_class = s3_storage_class(style)
write_options.merge!(:storage_class => storage_class) if storage_class
if #s3_server_side_encryption
write_options[:server_side_encryption] = #s3_server_side_encryption
end
style_specific_options = styles[style]
if style_specific_options
merge_s3_headers( style_specific_options[:s3_headers], #s3_headers, #s3_metadata) if style_specific_options[:s3_headers]
#s3_metadata.merge!(style_specific_options[:s3_metadata]) if style_specific_options[:s3_metadata]
end
write_options[:metadata] = #s3_metadata unless #s3_metadata.empty?
write_options.merge!(#s3_headers)
s3_object(style).write(file, write_options) unless style.to_s == "original" and #queued_for_write[:your_processed_style].present?
rescue AWS::S3::Errors::NoSuchBucket
create_bucket
retry
rescue AWS::S3::Errors::SlowDown
retries += 1
if retries <= 5
sleep((2 ** retries) * 0.5)
retry
else
raise
end
ensure
file.rewind
end
end
after_flush_writes # allows attachment to clean up temp files
#queued_for_write = {}
end
end
now the original does not get uploaded. You could then add some lines, like those of my origninal answer below, to your model if you wish to transfer the original to its appropriate final location if it was uploaded to s3 directly.
Original Answer:
perhaps something like this placed in your model executed with the after_create callback:
paperclip_file_path = "relative/final/destination/file.jpg"
s3.buckets[BUCKET_NAME].objects[paperclip_file_path].copy_from(relative/temp/location/file.jpg)
thanks to https://github.com/uberllama

Attachment ID, Logic

I have a Card, with this
try {
$menu_items = array();
$card = new \Google_Service_Mirror_TimelineItem();
//$card->setText("Test");
$card->setHtml('<img src="attachment:0"><img src="attachment:1">');
$menu_item = new \Google_Service_Mirror_MenuItem();
$menu_item->setAction("DELETE");
array_push($menu_items, $menu_item);
$card->setMenuItems($menu_items);
$opt_params = array();
$sr = $this->service->timeline->insert($card, $opt_params);
error_log('Send Card');
error_log(print_r($sr,true));
//return $sr;
$itemId = $sr->getId();
$params = array(
'data' => file_get_contents('https://XXXX.com/1.jpg'),
'mimeType'=>'image/jpg',
'uploadType' => 'media'
);
$sr = $this->service->timeline_attachments->insert($itemId, $params);
error_log('Send Card Attachment');
error_log(print_r($sr,true));
$params = array(
'data' => file_get_contents('https://XXXX.com/2.jpg'),
'mimeType'=>'image/jpg',
'uploadType' => 'media'
);
$sr = $this->service->timeline_attachments->insert($itemId, $params);
error_log('Send Card Attachment');
error_log(print_r($sr,true));
} catch (\Exception $e) {
error_log('Error while sending card '.$e->getMessage());
}
This works.
I get a card with two images.
Documentation states that I can use the attachments ids.. what is the logic behind that? for updates/patch only?
Aso, I am guessing if I send a card, and then I push the files, I would need to set notification.deliveryTime to the near future to avoid a weird card while the files are being uploaded?
It depends on the exact use. Some of the frameworks allow the attachments to be uploaded at the same time as the HTML for the card, so you'll be sure of the order and be sure that everything is available at once.
If you're uploading the attachments separately, it makes sense to use the attachment id that is returned when you do the upload since you have the information.
Good thought, but I wouldn't go with playing with notification.deliveryTime, since it hasn't worked very well the times I've tried using it. Instead, you might want to post the original card with some text such as "Loading..." and not send the notification at all. Then, when the attachments are uploaded, update the card to reference the attachments and set the notification so it generates the audio.
Update:
As you've noticed, you can't upload an attachment and attach it to multiple cards for the same reason you can't create a single timeline item and send it to multiple people - security. Attachments "belong" to a timeline item in the same way timeline items "belong" to a person. This is somewhat analogous to email and attachments - once you send the email out, each email has its own copy of the attachment.