View source with raw 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                 ]).