I'm still learning Grails and seem to have hit a stumbling block.
Here are the 2 domain classes:
class Photo {
byte[] file
static belongsTo = Profile
}
class Profile {
String fullName
Set photos
static hasMany = [photos:Photo]
}
The relevant controller snippet:
class PhotoController {
def viewImage = {
def photo = Photo.get( params.id )
byte[] image = photo.file
response.outputStream << image
}
}
Finally the GSP snippet:
<img class="Photo" src="${createLink(controller:'photo', action:'viewImage', id:'profileInstance.photos.get(1).id')}" />
Now how do I access the photo so that it will be shown on the GSP? I'm pretty sure that
profileInstance.photos.get(1).id is not correct.
If you have a url for the image, you just have to make sure you return the appropriate anser in the controller:
def viewImage= {
//retrieve photo code here
response.setHeader("Content-disposition", "attachment; filename=${photo.name}")
response.contentType = photo.fileType //'image/jpeg' will do too
response.outputStream << photo.file //'myphoto.jpg' will do too
response.outputStream.flush()
return;
}
As it is a Set, if you want the first element, you will have to go:
profileInstance.photos.toArray()[0].id
or
profileInstance.photos.iterator().next()
now, i actually think storing the photo as a binary blob in the database isnt the best solution - though you might have reasons why it needs to be done that way.
how about storing the name of the photo (and/or the path) instead? If name clashing issues are probable, use the md5 checksum of the photo as the name. Then the photo becomes a static resource, a simple file, instead of a more complicated and slower MVC request.
I´m learning grails too was searching for an example like this one.
The GSP snipplet didn´t work for me. I resolved by replacing the single quotes around profileInstance.photos.get(1).id
<img class="Photo" src="${createLink(controller:'photo', action:'viewImage', id:'profileInstance.photos.get(1).id')}" />
with double quotes:
<img class="Photo" src="${createLink(controller:'photo', action:'viewImage', id:"profileInstance.photos.get(1).id")}" />
Now grails resolves the expression around the double quotes. Otherwise it takes it as string.
My guess is you need to set the content type of the response stream. Something like:
response.ContentType = "image/jpeg"
This may or may not need to be before you stream to the response stream (can't imagine that it would matter). I'd just put it before the outputStream line in your code above.
id:'profileInstance.photos.get(1).id' should be id:profileInstance.photos.get(1).id. no quota
Related
In the end, my goal is to send a raw image data from the front-end, then split that image into however many pages, and lastly send that pdf back to the front-end for download.
But every time I use the theDoc.addImageFile(), it tells me that the "Image is not in a suitable format". I'm using this as reference: https://www.websupergoo.com/helppdfnet/source/5-abcpdf/doc/1-methods/addimagefile.htm
To troubleshoot, I thought that the image might not be rendering correctly, so I added a File.WriteAllBytes to view the rendered image and it was exactly what I wanted, but still not adding to the PDF. I also tried sending the actual path of a previously rendered image thinking that the new image might not have been fully created yet, but it also gave me the same error. Lastly, I thought PNGs might be problematic and changed to JPG but it did not work.
Here is the code:
[HttpPost]
public IActionResult PrintToPDF(string imageString)
{
// Converts dataUri to bytes
var base64Data = Regex.Match(imageString, #"data:image/(?<type>.+?),(?<data>.+)").Groups["data"].Value;
var binData = Convert.FromBase64String(base64Data);
/* Ultimately will be removed, but used for debugging image */
string path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string imgName= "Test.jpg";
string filename = Path.Combine(path, imgName);
System.IO.File.WriteAllBytes(filename, binData);
/***********************************************************/
using (Doc theDoc = new Doc())
{
// Using explicit path
theDoc.AddImageFile(#"C:\Users\User\Documents\Test.jpg", 1);
// Using variable
//theDoc.AddImageFile(filename, 1);
// What I really want
//theDoc.AddImageFile(binData , 1);
theDoc.Page = theDoc.AddPage();
theDoc.AddText("Thanks");
Response.Headers.Clear();
Response.Headers.Add("content-disposition", "attachment; filename=test.pdf");
return new FileStreamResult(theDoc.GetStream(), "application/pdf");
}
}
Try something like this (not tested, but cleaned up from my own code):
public int AddImageFile(Doc doc, byte[] data, int insertBeforePageID)
{
int pageid;
using (var img = new XImage())
{
img.SetData(data);
doc.Page = doc.AddPage(insertBeforePageID);
pageid = doc.Page;
doc.AddImage(img);
img.Clear();
}
return pageid;
}
To add a JPEG from a byte array you need Doc.AddImageData instead of Doc.AddImageFile. Note that AddImageFile / AddImageData do not support PNG - for that you would definitely need to use an XImage. The XImage.SetData documentation has the currently supported image formats.
I need to have force download action after clicking on Sitecore media link. I didn't find any useful Sitecore advance how to achieve that.
In Sitecore configuration you can define which mime types will be force downloaded. But problem is, if you want to do it for images. Because if you do this
<mediaType name="JPEG image" extensions="jpg, jpeg, jpe">
<mimeType>image/jpeg</mimeType>
<forceDownload>true</forceDownload>
</mediaType>
all images will disappear because of browser won't be able to present them in HTML!
How to make all Sitecore media files force downloaded on click?
After some investigation in Sitecore's code with decompiler I decide that the best way is to extend Sitecore MediaRequestHandler.
But how to recognize if I want to present an image and download an image? We have to add query string parameter to URL, something like "dl=1"
public class MediaRequestHandler : Sitecore.Resources.Media.MediaRequestHandler, System.Web.SessionState.IRequiresSessionState
{
protected override bool DoProcessRequest(HttpContext context, MediaRequest request, Media media)
{
// identify query string param from requested URL
bool isDownload = Utils.UrlUtils.HasQueryParam(context.Request.Url, "dl", "1");
if (isDownload)
{
using (MediaStream stream = media.GetStream())
{
// we have to check if file is not already force downloaded by Sitecore
if (!stream.ForceDownload)
{
string mediaName = media.MediaData.MediaItem.Name + "." + media.MediaData.Extension;
context.Response.AddHeader("Content-Disposition", "attachment; filename=\"" + mediaName + "\"");
}
context.Response.AddHeader("Content-Length", stream.Length.ToString());
}
}
// it must be called after all logic
return base.DoProcessRequest(context, request, media);
}
}
Some remarks for the code:
all logic must be before calling original DoProcessRequest method because after processing it will flush all headers to response (!)
there must be query string parameter which decide if to force download file or not
some mime types already have "forcedownload" attribute from configuration so you have to check it to avoid duplicate header "Content-Disposition" (this cause problem in Chrome, downloaded file will have extension something like "filename-, attachment")
I have this controller methods that depending on the parameters introduced by the user downloads a certain PDF file and shows a view with its different pages converted to PNG.
So the way I approached it works like this:
First I map a method to receive the post data sent by the user, then generate the URL of the actual PDF converter and pass it to the model:
#RequestMapping(method = RequestMethod.POST)
public String formPost(Model model, HttpServletRequest request) {
//Gather parameters and generate PDF url
Long idPdf = Long.parseLong(request.getParam("idPdf"));
//feed the jsp the url of the to-be-generated image
model.addAttribute("image", "getImage?idPdf=" + idPdf);
}
Then in getImageMethod I download the PDF and then generate a PNG out of it:
#RequestMapping("/getImage")
public HttpEntity<byte[]> getPdfToImage(#RequestParam Long idPdf) {
String url = "myPDFrepository?idPDF=" + idPdf;
URL urlUrl = new URL(url);
URLConnection urlConnection;
urlConnection = urlUrl.openConnection();
InputStream is = urlConnection.getInputStream();
return PDFtoPNGConverter.convert(is);
}
My JSP just has an img tag that refers to this url:
<img src="${image}" />
So far this work perfectly. But now I need to allow the possibility of viewing multi page PDFs, converted as PNGS, each of them in a different page. So I would add a page parameter, then feed my model with the image url including that page parameter, and in my getImage method I would convert only that page.
But the way it is implemented, I would be downloading the PDF again for each page, plus an additional time for the view, so it can find out whether this specific PDF has more pages and then show the "prev" and "next" buttons.
What would be a good way to preserve the same file during these requests, so I download it just once? I thought about using temp files but then managing its deletion might be a problem. So maybe storing the PDF in the session would be a good solution? I don't even know if this is good practice or not.
I am using Spring MVC by the way.
I think the simplest way would be using spring cache abstraction. Look at tutorial and will need to change your code a little: move logic that load pdf to separate class.
it will looks like:
interface PDFRepository {
byte[] getImage(long id);
}
#Repository
public class PDFRepositoryImpl implements PDFRepository {
#Cacheable
public byte[] getImage(long id) {
String url = "myPDFrepository?idPDF=" + idPdf;
URL urlUrl = new URL(url);
URLConnection urlConnection;
urlConnection = urlUrl.openConnection();
InputStream is = urlConnection.getInputStream();
return PDFtoPNGConverter.convert(is);
}
}
You will get pluggable cache implementation support and good cache expiration management.
I am a trying to parse and display images from a feed that has the imgage URL inside tags. An example is this:
*Note>> http://someImage.jpg is not a real image link, this is just an example. This is what I have done so far.
public void startElement(String uri, String localName, String qName, Attributes atts) {
chars = new StringBuilder();
if (qName.equalsIgnoreCase("content:encoded")) {
if (!atts.getValue("src").toString().equalsIgnoreCase("null")) {
feedStr.setImgLink(atts.getValue("src").toString());
Log.d(TAG, "inside if " + feedStr.getImgLink());
} else {
feedStr.setImgLink("");
Log.d(TAG, feedStr.getImgLink());
}
}
}
I believe this part of my programming needs to be tweaked. First, when qName is equal to "content:encoded" the parsing stops. The application just runs endlessly and displays nothing. Second, if I change that initial if to anything that qName cannot equal like "purplebunny" everything works perfect, except there will be no images. What am I missing? Am I using atts.getValue properly? I have used log to see what comes up in ImgLink and it is null always.
You can store the content:encoded data in a String. Then you can extract image by this library Jsoup
Example:
Suppose content:encoded raw data stored in Description variable.
Document doc = Jsoup.parse(Description);
Element image =doc.select("img").first();
String url = image.absUrl("src");
I am using the Background Transfer to upload Photographs to my Web Service. As the Photograph uploads can consume significant time and memory, I thought it might be a nice idea to use the background transfer request to accomplish this. After the photo is uploaded, I want to obtain the Id of the uploaded photo and then use it for post-processing. However, it turns out I can't do that in a background transfer request.
Per my understanding, Background Transfer works using the following logic ONLY:
You have to obtain the file you want to upload and then save/copy it to your app's Isolated Storage under the folder: shared/transfers. This is extremely important. Apparently, using file in a different location didn't work for me. Maybe it isn't the shared/transfers as much as it is a 'relative' path. But I would stick to the same conventions.
After you have saved the file in that location, your background request can be created based on that. It doesn't look like you can pass POST CONTENT other than the file contents, so any other parameters like file name, mime type etc. will need to be passed as QUERY String parameters only. I can understand this, but it would've been nice if I could pass both as POST Content. I don't think HTTP has a limitation on how this works.
Here is some code for creating a request using Hammock:
string url = App.ZineServiceAuthority + "articles/save-blob?ContainerName={0}&MimeType={1}&ZineId={2}&Notes={3}&IsPrivate={4}&FileName={5}";
url = String.Format(url, userId, "image/jpg", ZineId, txtStatus.Text, true, UploadFileName);
var btr = new BackgroundTransferRequest(new Uri(url, UriKind.Absolute));
btr.TransferPreferences = TransferPreferences.AllowCellularAndBattery;
btr.Method = "POST";
btr.Headers.Add("token", IsolatedStorageHelper.GetTravzineToken());
btr.UploadLocation = new Uri(#"/shared\transfers/" + UploadFileName, UriKind.Relative);
btr.TransferStatusChanged += new EventHandler<BackgroundTransferEventArgs>(btr_TransferStatusChanged);
btr.TransferProgressChanged += new EventHandler<BackgroundTransferEventArgs>(btr_TransferProgressChanged);
BackgroundTransferService.Add(btr);
In my case, I am literally passing all the necessary parameters using the query string. On a successful save, my Web Service returns back the Id of the Photo I just uploaded. However:
There is NO way (or at least I know of) to obtain and evaluate the RESPONSE. The Background Transfer Request Event handlers do not expose a RESPONSE.
Here are my event handlers:
void btr_TransferProgressChanged(object sender, BackgroundTransferEventArgs e)
{
bool isUploading = e.Request.TotalBytesToSend > 0 ? true : false;
lblStatus.Text = isUploading ? "Uploading" + e.Request.BytesSent.ToString() + " sent" : "Done";
}
void btr_TransferStatusChanged(object sender, BackgroundTransferEventArgs e)
{
if (e.Request.TransferStatus == TransferStatus.Completed)
{
using (IsolatedStorageFile iso =
IsolatedStorageFile.GetUserStoreForApplication())
{
if (iso.FileExists(e.Request.UploadLocation.OriginalString))
iso.DeleteFile(e.Request.UploadLocation.OriginalString);
}
BackgroundTransferService.Remove(e.Request);
if (null != e.Request.TransferError)
{
MessageBox.Show(e.Request.TransferError.Message);
}
else
{
lblStatus.Text = "Done baby done";
}
}
}
So now my question is, how does anyone do any sort of POST Processing in such scenarios?
Can anyone please tell me the line of thought behind designing such an inflexible class?
Any thoughts on how I could get around this issue would be appreciated.
Also, does anyone have any working examples of a homegrown BackgroundTransfer?
Haven't tried it but why not set a download location like this:
btr.DownloadLocation = "myDownloadFile.html";
btr.UploadLocation = "myUploadFile.jpg";
...
If the request is completed read the file "myDownloadFile.html" where your response has been stored and delete it afterwards.