View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2009-2014, VU University Amsterdam
    7    All rights reserved.
    8
    9    Redistribution and use in source and binary forms, with or without
   10    modification, are permitted provided that the following conditions
   11    are met:
   12
   13    1. Redistributions of source code must retain the above copyright
   14       notice, this list of conditions and the following disclaimer.
   15
   16    2. Redistributions in binary form must reproduce the above copyright
   17       notice, this list of conditions and the following disclaimer in
   18       the documentation and/or other materials provided with the
   19       distribution.
   20
   21    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   22    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   23    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   24    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   25    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   26    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   27    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   29    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   30    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   31    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   32    POSSIBILITY OF SUCH DAMAGE.
   33*/
   34
   35:- module(http_dirindex,
   36          [ http_reply_dirindex/3,      % +PhysicalDir, +Options, +Request
   37            directory_index//2          % +PhysicalDir, +Options
   38          ]).   39:- use_module(library(http/html_write)).   40:- use_module(library(http/http_path)).   41:- use_module(library(http/http_dispatch)).   42:- use_module(library(http/http_server_files)).   43:- use_module(library(http/html_head)).   44:- use_module(library(http/mimetype)).   45:- use_module(library(apply)).   46:- use_module(library(option)).   47
   48:- predicate_options(http_reply_dirindex/3, 2,
   49                     [ title(any),
   50                       pass_to(http_dispatch:http_safe_file/2, 2)
   51                     ]).   52
   53/** <module> HTTP directory listings
   54
   55This module provides a simple API to   generate  an index for a physical
   56directory. The index can be customised   by  overruling the dirindex.css
   57CSS file and by defining  additional  rules   for  icons  using the hook
   58http:file_extension_icon/2.
   59
   60@tbd    Provide more options (sorting, selecting columns, hiding files)
   61*/
   62
   63%!  http_reply_dirindex(+DirSpec, +Options, +Request) is det.
   64%
   65%   Provide a directory listing for Request, assuming it is an index
   66%   for the physical directrory Dir. If   the  request-path does not
   67%   end with /, first return a moved (301 Moved Permanently) reply.
   68%
   69%   The  calling  conventions  allows  for    direct   calling  from
   70%   http_handler/3.
   71
   72http_reply_dirindex(DirSpec, Options, Request) :-
   73    http_safe_file(DirSpec, Options),
   74    absolute_file_name(DirSpec, Dir,
   75                       [ file_type(directory),
   76                         access(read)
   77                       ]),
   78    memberchk(path(Path), Request),
   79    (   atom_concat(PlainPath, /, Path),
   80        merge_options(Options,
   81                      [ title(['Index of ', PlainPath]) ],
   82                      Options1)
   83    ->  dir_index(Dir, Options1)
   84    ;   atom_concat(Path, /, NewLocation),
   85        throw(http_reply(moved(NewLocation)))
   86    ).
   87
   88dir_index(Dir, Options) :-
   89    directory_members(Dir, SubDirs, Files),
   90    option(title(Title), Options, Dir),
   91    reply_html_page(
   92        dir_index(Dir, Title),
   93        title(Title),
   94        [ h1(Title),
   95          \dirindex_table(SubDirs, Files, Options)
   96        ]).
   97
   98directory_members(Dir, Dirs, Files) :-
   99    atom_concat(Dir, '/*', Pattern),
  100    expand_file_name(Pattern, Matches),
  101    partition(exists_directory, Matches, Dirs, Files).
  102
  103%!  directory_index(+Dir, +Options)// is det.
  104%
  105%   Show index for a directory.  Options processed:
  106%
  107%     * order_by(+Field)
  108%     Sort the files in the directory listing by Field.  Field
  109%     is one of =name= (default), =size= or =time=.
  110%     * order(+AscentDescent)
  111%     Sorting order.  Default is =ascending=.  The altenative is
  112%     =descending=
  113
  114directory_index(Dir, Options) -->
  115    { directory_members(Dir, SubDirs, Files) },
  116    dirindex_table(SubDirs, Files, Options).
  117
  118dirindex_table(SubDirs, Files, Options) -->
  119    { option(order_by(By), Options, name),
  120      sort_files(By, Files, SortedFiles0),
  121      asc_desc(SortedFiles0, SortedFiles, Options)
  122    },
  123    html_requires(http_dirindex),
  124    html(table(class(dirindex),
  125               [ \dirindex_title,
  126                 \back
  127               | \dirmembers(SubDirs, SortedFiles)
  128               ])).
  129
  130sort_files(name, Files, Files) :- !.
  131sort_files(Order, Files, Sorted) :-
  132    map_list_to_pairs(key_file(Order), Files, Pairs),
  133    keysort(Pairs, SortedPairs),
  134    pairs_values(SortedPairs, Sorted).
  135
  136key_file(name, File, Base) :-
  137    file_base_name(File, Base).
  138key_file(size, File, Size) :-
  139    size_file(File, Size).
  140key_file(time, File, Time) :-
  141    time_file(File, Time).
  142
  143asc_desc(Files, Ordered, Options) :-
  144    (   option(order(ascending), Options, ascending)
  145    ->  Ordered = Files
  146    ;   reverse(Files, Ordered)
  147    ).
  148
  149dirindex_title -->
  150    html(tr(class(dirindex_header),
  151            [ th(class(icon),     ''),
  152              th(class(name),     'Name'),
  153              th(class(modified), 'Last modified'),
  154              th(class(size),     'Size')
  155            ])).
  156
  157back -->
  158    html(tr([ \icon_cell('back.png', '[UP]'),
  159              \name_cell(.., 'Up'),
  160              td(class(modified), -),
  161              td(class(size),     -)
  162            ])).
  163
  164dirmembers(Dirs, Files) -->
  165    dir_rows(Dirs, odd, End),
  166    file_rows(Files, End, _).
  167
  168dir_rows([], OE, OE) --> [].
  169dir_rows([H|T], OE0, OE) -->
  170    dir_row(H, OE0),
  171    { oe(OE0, OE1) },
  172    dir_rows(T, OE1, OE).
  173
  174file_rows([], OE, OE) --> [].
  175file_rows([H|T], OE0, OE) -->
  176    file_row(H, OE0),
  177    {oe(OE0, OE1)},
  178    file_rows(T, OE1, OE).
  179
  180oe(odd, even).
  181oe(even, odd).
  182
  183dir_row(Dir, OE) -->
  184    { file_base_name(Dir, Name)
  185    },
  186    html(tr(class(OE),
  187            [ \icon_cell('folder.png', '[DIR]'),
  188              \name_cell(Name, Name),
  189              \modified_cell(Dir),
  190              td(class(size), -)
  191            ])).
  192
  193
  194file_row(File, OE) -->
  195    { file_base_name(File, Name),
  196      file_mime_type(File, MimeType),
  197      mime_type_icon(MimeType, IconName),
  198      uri_encoded(path, Name, Ref)
  199    },
  200    html(tr(class(OE),
  201            [ \icon_cell(IconName, '[FILE]'),
  202              \name_cell(Ref, Name),
  203              \modified_cell(File),
  204              td(class(size), \size(File))
  205            ])).
  206
  207icon_cell(IconName, Alt) -->
  208    { http_absolute_location(icons(IconName), Icon, [])
  209    },
  210    html(td(class(icon), img([src(Icon), alt(Alt)]))).
  211
  212
  213name_cell(Ref, Name) -->
  214    html(td(class(name), a(href(Ref), Name))).
  215
  216
  217modified_cell(Name) -->
  218    { time_file(Name, Stamp),
  219      format_time(string(Date), '%+', Stamp)
  220    },
  221    html(td(class(modified), Date)).
  222
  223size(Name) -->
  224    { size_file(Name, Size)
  225    },
  226    html('~D'-[Size]).
  227
  228%!  mime_type_icon(+MimeType, -Icon) is det.
  229%
  230%   Determine the icon that is used  to   show  a  file of the given
  231%   extension. This predicate can  be   hooked  using  the multifile
  232%   http:mime_type_icon/2 hook with the same  signature. Icon is the
  233%   plain name of an image file that appears in the file-search-path
  234%   =icons=.
  235%
  236%   @param  MimeType  is  a  term    Type/SubType   as  produced  by
  237%   file_mime_type/2.
  238
  239mime_type_icon(Ext, Icon) :-
  240    http:mime_type_icon(Ext, Icon),
  241    !.
  242mime_type_icon(_, 'generic.png').
  243
  244%!  http:mime_type_icon(+MimeType, -IconName) is nondet.
  245%
  246%   Multi-file hook predicate that can be used to associate icons to
  247%   files listed by http_reply_dirindex/3. The   actual icon file is
  248%   located by absolute_file_name(icons(IconName), Path, []).
  249%
  250%   @see serve_files_in_directory/2 serves the images.
  251
  252:- multifile
  253    http:mime_type_icon/2.  254
  255http:mime_type_icon(application/pdf,      'layout.png').
  256http:mime_type_icon(text/csrc,            'c.png').
  257http:mime_type_icon(application/'x-gzip', 'compressed.png').
  258http:mime_type_icon(application/'x-gtar', 'compressed.png').
  259http:mime_type_icon(application/zip,      'compressed.png').
  260
  261
  262                 /*******************************
  263                 *            RESOURCES         *
  264                 *******************************/
  265
  266:- html_resource(http_dirindex,
  267                 [ virtual(true),
  268                   requires([ css('dirindex.css')
  269                            ])
  270                 ]).