Saving favorite pictures
We will now add a button to the Graffiti application to save the current image. The images will be saved to the filesystem using the module Lwt_io. We will then make an Atom feed with the saved images using Syndic.
To install it, do:
opam install syndic
We save the images in the directory containing the static contents under the directory images_saved/username. The username directory is created if needed. If it already exists mkdir fails and we do nothing.
We will add this code in a new file:
feed.ml
open Eliom_content
open Lwt.Infix
open Html.F
open Server
let static_dir =
match Eliom_config.get_config () with
| [Element ("staticdir", [], [PCData dir])] ->
dir
| [] ->
raise (Ocsigen_extensions.Error_in_config_file
("<staticdir> option required for <graffiti>"))
| _ ->
raise (Ocsigen_extensions.Error_in_config_file
("Unexpected content inside graffiti config"))
let create_dir dir =
try%lwt Lwt_unix.mkdir dir 0o777 with
| Unix.Unix_error (Unix.EEXIST, "mkdir", _) -> Lwt.return_unit
| _ ->
Eliom_lib.debug "could not create the directory %s" dir;
Lwt.return_unit
let image_dir name =
let dir = static_dir ^ "/graffiti_saved/" in
let%lwt () = create_dir dir in
let dir = dir ^ Eliom_lib.Url.encode name in
let%lwt () = create_dir dir in
Lwt.return dir
let make_filename name number =
image_dir name >|= ( fun dir -> (dir ^ "/" ^ (string_of_int number) ^ ".png") )
let save image name number =
let%lwt file_name = make_filename name number in
let%lwt out_chan = Lwt_io.open_file ~mode:Lwt_io.output file_name in
Lwt_io.write out_chan image
We number images and associate to each image the time of creation. It is stocked in an Ocsipersist table.
let image_info_table = Ocsipersist.Polymorphic.open_table "image_info_table"
For each user, we stock a value of type
int * CalendarLib.Calendar.t * ((int * CalendarLib.Calendar.t) list).
The first integer is the name under which will be saved the image, the first
time is the last update for that user and the list contains the names and
times of old images. We need those times to timestamp the entries of the feed.
let save_image username =
let now = CalendarLib.Calendar.now () in
let%lwt image_info_table = image_info_table in
let%lwt number,_,list =
try%lwt Ocsipersist.Polymorphic.find image_info_table username with
| Not_found -> Lwt.return (0,now,[])
| e -> Lwt.fail e
in
let%lwt () = Ocsipersist.Polymorphic.add image_info_table
username (number+1,now,(number,now)::list) in
let (_,image_string) = Hashtbl.find graffiti_info username in
save (image_string ()) username number
let save_image_service =
Eliom_service.create
~meth:(Eliom_service.Post
(Eliom_parameter.unit, Eliom_parameter.string "name"))
~path:Eliom_service.No_path ()
let () =
Eliom_registration.Action.register
~service:save_image_service (fun () name -> save_image name)
let save_image_box name =
Lwt.return
(Html.D.Form.post_form ~service:save_image_service
(fun param_name ->
[p [Html.D.Form.input ~input_type:`Hidden ~name:param_name
~value:name Html.D.Form.string;
Html.D.Form.button_no_value ~button_type:`Submit [txt "save"]]])
())
We find the url of the images with Eliom_service.static_dir. It is a service taking file path as parameter, serving the content of the static directory. We use Eliom_uri.make_string_uri to get the url as a string.
let feed_service =
Eliom_service.create
~path:(Eliom_service.Path ["feed"])
~meth:(Eliom_service.Get (Eliom_parameter.string "name"))
()
let local_filename name number =
["graffiti_saved"; Eliom_lib.Url.encode name ; (string_of_int number) ^ ".png"]
let rec entries name list = function
| 0 -> []
| len ->
match list with
| [] -> []
| (n,saved)::q ->
let uri =
Html.D.make_uri ~absolute:true
~service:(Eliom_service.static_dir ())
(local_filename name n)
|> Xml.string_of_uri
|> Uri.of_string in
let content = Syndic.Atom.Src (None, uri) in
let authors = (Syndic.Atom.author name), [] in
let title : Syndic.Atom.text_construct =
Syndic.Atom.Text ("graffiti " ^ name ^ " " ^ (string_of_int n)) in
let entry =
Syndic.Atom.entry ~content ~id:uri ~authors ~title ~updated:saved () in
entry::(entries name q (len - 1))
let feed_of_string_page xml =
xml
|> Syndic.Atom.to_xml
|> Syndic.XML.to_string
|> fun string -> string, ""
let feed name () =
let id =
Xml.string_of_uri
(Html.D.make_uri ~absolute:true ~service:feed_service name)
|> Uri.of_string in
let title : Syndic.Atom.text_construct =
Syndic.Atom.Text ("nice drawings of " ^ name) in
Lwt.catch
(fun () ->
let%lwt image_info_table = image_info_table in
Ocsipersist.Polymorphic.find image_info_table name >|=
(fun (number,updated,list) ->
Syndic.Atom.feed ~id ~updated ~title (entries name list 10)
|> feed_of_string_page))
( function Not_found ->
let now = Option.get (Ptime.of_float_s (Unix.gettimeofday ())) in
Lwt.return (Syndic.Atom.feed ~id ~updated:now ~title []
|> feed_of_string_page)
| e -> Lwt.fail e )
let () = Eliom_registration.String.register ~service:feed_service feed
And then use the new module as follow:
[ a Feed.feed_service [txt "atom feed"] name;
div (if name = username
then [Feed.save_image_box name]
else [txt "no saving"]);
]