pdf uploaded to linux will not load

  • Function 1: Footstep-past-Step Tutorial to Build a Phoenix App that Supports User Upload
  • Part 2: this article
  • Office 3: Add a Progress Bar in the Phoenix File Upload app

In Step-by-Step Tutorial to Build a Phoenix App that Supports User Upload nosotros've built from scratch a Phoenix application where users tin can upload and download their files. We saw how to deal with uploads, saving them locally, using Ecto and Postgres database to save file'southward details like size, filename, hash etc.

We are now going to add a new layer, where we create thumbnails for image and PDF files.

Permit'south see speedily how to get the previous part's lawmaking upwards and running, and then we can start building from it. If you lot already have everything setup, y'all can skip this part and go directly to ImageMagick.

            $ git clone git@github.com:poeticoding/phoenix_uploads_articles.git  $ cd phoenix_uploads_articles  $ git checkout tags/part-i                      

To exist on the aforementioned page, once cloned the repository, let'south checkout to the commit with tag part-1, which is the situation at the cease of previous article.

We then need to get the app dependencies and build the javascript and css assets.

            $ mix deps.become && mix deps.compile $ cd assets && npm install && \   node node_modules/webpack/bin/webpack.js \   --fashion development                      

We need a Postgres server upwards and running, where we save the uploaded files details. In Postgres using Docker yous tin run into in detail how to run a Postgres server with Docker on your local automobile.

Once the the Postgres server is set up, nosotros can create the database and uploads table using the Ecto mix tasks.

            $ mix ecto.create Generated poetic app The database for Poetic.Repo has been created  $ mix ecto.migrate [info] == Running 20190412141226 Poetic.Repo.Migrations.CreateUploads.alter/0 frontward [info] create tabular array uploads [info] create index uploads_hash_index [info] == Migrated 20190412141226 in 0.0s                      

One last thing – at the bottom of config/dev.exs you discover the uploads_directory choice fix to my local directory /Users/alvise/uploads_dev. Information technology's important you create a binder where to shop the uploads (better outside the project'south directory) and prepare the accented path in the configuration file. Yous could likewise set it using an environment variable (which is the best way in production).

We are at present gear up to run the Phoenix server and upload a file.

            $ mix phx.server [info] Running PoeticWeb.Endpoint with cowboy two.6.3 at 0.0.0.0:4000 (http) [info] Access PoeticWeb.Endpoint at http://localhost:4000 ...                      
uploading a file
Uploading files

ImageMagick

To create images' and PDF's thumbnails, we need to install ImageMagick. If yous never used ImageMagick before, it's i of the most famous and used tools to process images on a web backend. Nosotros volition use information technology to read any image format and convert it to a resized jpeg that we tin can brandish in the uploads page.

On my mac I've installed it using Homebrew, this is definitely the easiest way to install it on macOS.

            $ brew install imagemagick $ magick --version Version: ImageMagick 7.0.viii-44 Q16 x86_64 ... ...                      

If you lot are on linux, information technology depends which distribution y'all are using. Y'all can install it using the binaries available on the ImageMagick website, Unix Binary Release. If you lot are using Debian or Ubuntu you can use apt-get

            # Ubuntu 18.04 $ apt-get install imagemagick $ mogrify -version Version: ImageMagick 6.9.seven-4 Q16 x86_64 20170114 ...                      

Ubuntu 18.04, for case, has a slightly older ImageMagick version, which is not an event for what nosotros need to practice.

ImageMagick vi.ix doesn't have the magick command like in the Mac installation. To check the version yous tin can run the mogrify command, which is ane of the ImageMagick tools and the one we'll apply to create thumbnails.

Create thumbnails with Mogrify in Elixir

Mogrify is a wrapper effectually the ImageMagick mogrify command, which brings a great Elixir-like experience on processing images. We add it in our mix.exs nether deps

            $ mix deps.get Resolving Hex dependencies... Dependency resolution completed: ...   mogrify 0.seven.2 ...                      

The only Mogrify'southward dependency is ImageMagick, which we've already installed.

Yous tin download the phoenix_uploads.png image and save it into the assets/static/images/ folder, then you can employ it do some resizing experiments in the Elixir's interactive shell iex.

            $ iex -South mix iex> Mogrify.open("assets/static/images/phoenix_uploads.png") \ ...> |> Mogrify.resize_to_limit("300x300") \ ...> |> Mogrify.save(path: "phoenix_uploads_thumb.jpg") %Mogrify.Image{   animated: false,   buffer: naught,   muddy: %{},   ext: ".jpg",   format: nil,   frame_count: 1,   height: zip,   operations: [],   path: "phoenix_uploads_thumb.jpg",   width: goose egg }                      
  • with resize_to_limit("300x300") the paradigm is resized maintaining proportions, limiting the width and hight to a maximum of 300 pixels.
  • with Mogrify.salve(path: "phoenix_uploads_thumb.jpg") nosotros start the processing. The library at this indicate runs the ImageMagick mogrify control. With the :path option we specify the path where we want to store the file, and using a different extension (like .jpg in this instance) the image is converted to the new image format.

We detect our resized image in the root directory of the project. We can use Mogrify to check width and hight of this new image.

            iex> ls ... phoenix_uploads_thumb.jpg  iex> Mogrify.open("phoenix_uploads_thumb.jpg") \ ...> Mogrify.verbose() %Mogrify.Image{   ...   ext: ".jpg",   format: "jpeg",   height: 199,   width: 300   path: ... }                      

thumbnail? field in Upload Ecto Schema

The user should be able to upload whatever file format, but we can create thumbnails just for images and PDF. We need and so to have a new field in Upload schema reflecting a boolean column in our database, where we store if the upload has a thumbnail or not.

We are going to change the electric current uploads database table, adding a new has_thumb boolean cavalcade. To do and so, we generate a new Ecto migration

            $ mix ecto.gen.migration add_uploads_has_thumb * creating priv/repo/migrations/20190514143331_add_uploads_has_thumb.exs                      

The task creates a migration template file with an empty change function we need to fill.

            # priv/repo/migrations/20190514143331_add_uploads_has_thumb.exs defmodule Poetic.Repo.Migrations.AddUploadsHasThumb do   utilise Ecto.Migration    def change exercise     alter tabular array(:uploads) practice       add together :has_thumb, :boolean, default: faux     end   end stop                      

In this way nosotros alter the uploads table adding the has_thumb column. All the existing records will have this value gear up to faux.

To apply this migration we run the migration job

            $  mix ecto.migrate [info] == Running 20190514143331 Poetic.Repo.Migrations.AddUploadsHasThumb.change/0 frontward [info] alter table uploads [info] == Migrated 20190514143331 in 0.0s                      

Let's add the new thumbnail? field in the uploads schema divers in the Poetic.Documents.Upload module, mapping this field to the has_thumb column, using the :source choice

            # lib/poetic/documents/upload.ex defmodule Poetic.Documents.Upload do   schema "uploads" do     ...     field :thumbnail?, :boolean, source: :has_thumb     ...   end end                      

Upload module and changeset

Later on calculation the thumbnail? field to the schema, it's now time to write the functions where we create the thumbnail and update the upload database record.

Permit'southward start from the Upload.changeset/ii part. As it is, if we try to modify the thumbnail? value, nothing happens

            iex> alias Poetic.Documents.Upload iex> Upload.changeset(%Upload{},%{thumbnail?: truthful}) #Ecto.Changeset<   action: nil,   changes: %{},   ... >                      

Nosotros need to add the :thumbnail? atom to the valid fields passed to cast

            # lib/poetic/documents/upload.ex def changeset(upload, attrs) do   upload   |> cast(attrs, [:filename, :size, :content_type,            :hash, :thumbnail?])   ... end  # on iex iex> Upload.changeset(%Upload{}, %{thumbnail?: true}) #Ecto.Changeset<   activity: zero,   changes: %{thumbnail?: true},   ... >                      

Thumbnails path

Now, where do nosotros store our thumbnails?

We could store them in priv/static/images directory of the projection, in this way we would utilize the Plug.Static plug that serves static avails. But copying the the image into the priv/static/images directory, the thumbnail could be downloaded at the URL http://localhost:4000/images/thumbnail_filename.jpg. In this way is really like shooting fish in a barrel to serve new images. Plug.Static likewise uses etags for HTTP caching.

Simply we go in a dissimilar direction, saving thumbnails into the same directory where we shop the uploaded files. In this way we put everything together and we tin accept a ameliorate command on serving these thumbnails based on user'southward permission.

Let's now define a thumbnail_path/1 part that returns a valid and unique accented path for our thumbnail

            # lib/poetic/documents/upload.ex def thumbnail_path(id) practise   [@upload_directory, "pollex-#{id}.jpg"]   |> Path.join() cease                      

Equally we did for the local_path/2, we use the id to uniquely place the thumbnail. In general information technology's a practiced idea to have a defended directory for each single upload – in this mode we tin have the freedom to group multiple files that refer to the aforementioned upload, without overcrowding the uploads directory. Since this adds a bit of complexity (for example creating a directory for each upload), nosotros save the thumbnails straight in the @upload_directory.

Image'southward thumbnail

To generate thumbnails we utilise a Mogrify lawmaking like to what we've tried before on iex

            # lib/poetic/documents/upload.ex  def mogrify_thumbnail(src_path, dst_path) do    try practise      Mogrify.open(src_path)      |> Mogrify.resize_to_limit("300x300")      |> Mogrify.salve(path: dst_path)    rescue      File.Error -> {:error, :invalid_src_path}      error -> {:fault, error}    else      _image -> {:ok, dst_path}    end  end                      

In full general I prefer to avoid the endeavor/rescue construct to handle errors, it's usually better to pattern match {:error, _} {:ok, _} tuples.

The Mogrify.open/1 function raises a File.Error when the source image file doesn't exist, so nosotros demand to apply try/rescue to properly handle the error and return a tuple.

At the moment, with the current Mogrify (version 0.7.2) , Mogrify.relieve/2 doesn't return or raise an mistake if information technology fails, which is not something that tin can exist left to fate. We will run into later, when creating thumbnails for PDF files, how to run ImageMagick commands in Elixir handling open and save errors.

Using pattern matching nosotros tin can define a create_thumbnail/1 function for each content_type we desire. We start with epitome content blazon to support unlike image formats (image/png, epitome/jpg etc.)

            # lib/poetic/documents/upload.ex  def create_thumbnail(%__MODULE__{   content_type: "image/" <> _img_type }=upload) do    original_path = local_path(upload.id, upload.filename)   thumb_path = thumbnail_path(upload.id)   {:ok, _} = mogrify_thumbnail(original_path, thumb_path)    changeset(upload, %{thumbnail?: true}) end  def create_thumbnail(%__MODULE__{}=upload),  	do: changeset(upload, %{})                      

The function arguments is an %Upload{} struct, with content_type starting with prototype/. We use the id and filename fields to become the original file path and thumbnail_path/ane function to get the destination path.

For simplicity, we expect that mogrify_thumbnail/ii returns a {:ok, _} tuple – when it returns {:mistake, _} a MatchError is raised. If unhandled, it can lead to a HTTP 500 Internal Server Error fault bulletin that is shown to the user. In that location are many ways of handling these type of errors: we could silently fail and gear up a generic thumbnail, or render an Ecto Changeset with an error on the thumbnail? field etc. At the end information technology depends on how we want our app to bear in these situations.

At the function's last line, we catechumen the upload struct to an Ecto changeset, setting the thumbnail? value to true. This function doesn't apply whatever change to the upload's tape – to persist this change we will pass the changeset to Repo.update/1 function.

The 2d create_thumbnail/1 it'south a catchall clause. Information technology handles the cases where the file is non an image. Information technology merely returns a changeset with no changes.

Documents context

Now that nosotros have everything we demand to create our thumbnail, we simply need to decide in which part of the code we want to generate it.

The easiest way is adding a new stride inside the Documents.create_upload_from_plug_upload/ane function. We append a new stride to the with in the transaction.

            # lib/poetic/documents.ex defmodule Poetic.Documents do    def create_upload_from_plug_upload(%Plug.Upload{...}) exercise     ...      	with ..., 	 {:ok, upload} <-  		 ... |> Repo.insert(), 	 ...      {:ok, upload} <-  	     Upload.create_thumbnail(upload) |> Repo.update()      do       ...     else       ...     stop      ...   finish cease                      

If nosotros now run our app and upload an prototype, we nevertheless don't see any thumbnail shown in the uploads folio – just looking inside the uploads folder, we come across that the thumbnail has been successfully created during the upload process.

Thumbnail creation working correctly
Thumbnail creation working correctly

Serve and show the thumbnails

Nosotros've just seen that the Documents context successfully creates thumbnails for image files, only these aren't shown in the uploads page. Since thumbnail images are in the uploads directory, to serve them nosotros demand first to add a route in the router and an activity in the PoeticWeb.UploadController.

            # lib/poetic_web/router.ex ... scope "/", PoeticWeb exercise   resources "/uploads", UploadController, simply: [:alphabetize, :new, :create, :show] exercise     get "/thumbnail", UploadController, :thumbnail, as: "thumbnail"   end end                      
            $ mix phx.routes ... upload_thumbnail_path  Become  /uploads/:upload_id/thumbnail \       PoeticWeb.UploadController :thumbnail ...                      

Inside the uploads resources, nosotros ascertain a get road, which points to the UploadController.thumbnail action.

With mix phx.routes nosotros see that the new route is listed and the upload_thumbnail_path helper is created. Discover that the cardinal used to the pass the id in the parameters is at present upload_id.

In the UploadController we write the thumbnail/2 office

            # lib/poetic_web/controllers/upload_controller.ex def thumbnail(conn, %{"upload_id" => id}) practice   thumb_path = Upload.thumbnail_path(id)    conn   |> put_resp_content_type("image/jpeg")   |> send_file(200, thumb_path) end                      

We pattern match the upload_id and get the thumbnail path. We then gear up the content type to image/jpeg and transport the file to the client. Nosotros can see immediately if it works, uploading a file and making a Go request with the browser

GET /uploads/1/thumbnail
Get /uploads/1/thumbnail

Permit's now change the lib/poetic_web/templates/upload/alphabetize.html.eex file to show the thumbnail in the list

            <%= for upload <- @uploads do %>  <tr>   <td>     <%= if upload.thumbnail? do        img_tag Routes.upload_thumbnail_path(@conn, :thumbnail, upload.id)     else       img_tag "/images/generic_thumbnail.jpg"     end %>   </td> 	       <td>...</td>      </tr>  <% end %>                      
  • If the upload has a thumbnail, we testify the thumbnail using img_tag and the upload_thumbnail_path helper (which returns the path to become the thumbnail for the given upload id).
  • in the instance it hasn't a thumbnail, we show a generic thumbnail placed in assets/static/images.

PDF'southward thumbnail

Until at present, nosotros've just focused on thumbnail of images. In the case of PDF files, we can create a thumbnail of the first page.

The all-time tool to create an image from a page of a PDF is the ImageMagick's convert command, which isn't covered by the Mogrify wrapper.

Then we need to build our Elixir function that runs the convert command, based on System.cmd/3.

Commencement, let's run into how we create a PDF's thumbnail

            # on linux/mac/unix $ convert -density 300 -resize 300x300 \   'programming-phoenix.pdf[0]' \   'pdf_thumb.jpg'                      

The [0] afterward the pdf path is used to choose which folio we desire to convert, starting from 0.

If convert returns an mistake like catechumen: no images defined it could be related to a an consequence of ghostscript 9.26, which is used to process PDFs. To temporarily fix this event, waiting for the next version, I've downgraded ghostscript to version 9.25.

            # on mac $ brew uninstall ghostscript $ brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/b0b6b9495113b0ac5325e07d5427dc89170f690f/Formula/ghostscript.rb                      

On Windows, from ImageMagick 7, the command is a little dissimilar. We need to prepend the magick

Thumbnail of a PDF
Thumbnail of a PDF
            # lib/poetic/documents/upload.ex def pdf_thumbnail(pdf_path, thumb_path) practice   args = ["-density", "300", "-resize",            "300x300","#{pdf_path}[0]",            thumb_path]    example System.cmd("catechumen", args, stderr_to_stdout: true) exercise     {_, 0} -> {:ok, thumb_path}       {reason, _} -> {:error, reason}   end end                      

The arguments are passed to Organization.cmd/three every bit a list and we enable the stderr_to_stdout selection to take hold of anything printed by the control.

  • in the case convert exits with 0, information technology means the thumbnail was created correctly.
  • for any other exit lawmaking, we return {:error, reason} where reason is the command's output string

If nosotros try to make it neglect, providing an invalid PDF path, nosotros become an {:mistake, reason} tuple as expected.

            iex> Poetic.Documents.Upload.pdf_thumbnail(   "invalid.pdf", "thumb.jpg" )  {:mistake,  "convert: unable to open epitome 'invalid.pdf': No such file or  directory @ fault/blob.c/OpenBlob/3497.\nconvert: no images defined `thumb.jpg' @ error/convert.c/ConvertImageCommand/3300.\due north"}                      

Since we are catching all the output on stdout and stderr, the error string could be a scrap too verbose. Nosotros can procedure the string, for example getting just the first line until the @ symbol.

Same for an invalid output path

            iex> Poetic.Documents.Upload.pdf_thumbnail(   "programming-phoenix.pdf",    "/tmp/invalid/thumb.jpg")  {:error,  "catechumen: unable to open up image '/tmp/invalid/thumb.jpg': No such file or directory @ mistake/blob.c/OpenBlob/3497.\n"}                      

Nosotros can now utilise this function to extend the support to the awarding/pdf content type.

            # lib/poetic/documents/upload.ex def create_thumbnail(%__MODULE__{   content_type: "application/pdf" }=upload) do 	original_path = local_path(upload.id, upload.filename) 	thumb_path = thumbnail_path(upload.id) 	{:ok, _} = pdf_thumbnail(original_path, thumb_path) 	changeset(upload, %{thumbnail?: true}) end                      

We meet that once added this new create_thumbnail clause into Upload module, the PDF'southward thumbnails are created and shown successfully

Uploaded PDFs with thumbnails

Total code of this office

You can detect the full code of this article on the poeticoding/phoenix_uploads_articles GitHub repo.

curryrehaddeed.blogspot.com

Source: https://www.poeticoding.com/creating-thumbnails-of-uploaded-images-and-pdf-in-phoenix/

0 Response to "pdf uploaded to linux will not load"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel