34
35:- module(http_dirindex,
36 [ http_reply_dirindex/3, 37 directory_index//2 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:- meta_predicate
49 http_reply_dirindex(+, :, +),
50 directory_index(+, :, ?, ?). 51
52:- predicate_options(http_reply_dirindex/3, 2,
53 [ title(any),
54 pass_to(http_dispatch:http_safe_file/2, 2),
55 pass_to(directory_index/4, 2)
56 ]). 57:- predicate_options(directory_index/4, 2,
58 [ name(callable),
59 order_by(oneof([name,size,time])),
60 order(oneof([ascending,descending]))
61 ]). 62
72
81
82http_reply_dirindex(DirSpec, Options0, Request) :-
83 meta_options(is_meta, Options0, Options),
84 http_safe_file(DirSpec, Options),
85 absolute_file_name(DirSpec, Dir,
86 [ file_type(directory),
87 access(read)
88 ]),
89 memberchk(path(Path), Request),
90 ( atom_concat(PlainPath, /, Path),
91 merge_options(Options,
92 [ title(['Index of ', PlainPath]) ],
93 Options1)
94 -> dir_index(Dir, Options1)
95 ; atom_concat(Path, /, NewLocation),
96 throw(http_reply(moved(NewLocation)))
97 ).
98
99is_meta(name).
100
101dir_index(Dir, Options) :-
102 directory_members(Dir, SubDirs, Files),
103 option(title(Title), Options, Dir),
104 reply_html_page(
105 dir_index(Dir, Title),
106 title(Title),
107 [ h1(Title),
108 \dirindex_table(SubDirs, Files, Options)
109 ]).
110
111directory_members(Dir, Dirs, Files) :-
112 atom_concat(Dir, '/*', Pattern),
113 expand_file_name(Pattern, Matches),
114 partition(exists_directory, Matches, Dirs, Files).
115
128
129directory_index(Dir, Options0) -->
130 { meta_options(is_meta, Options0, Options),
131 directory_members(Dir, SubDirs, Files)
132 },
133 dirindex_table(SubDirs, Files, Options).
134
135dirindex_table(SubDirs, Files, Options) -->
136 { option(order_by(By), Options, name),
137 sort_files(By, Files, SortedFiles0),
138 asc_desc(SortedFiles0, SortedFiles, Options)
139 },
140 html_requires(http_dirindex),
141 html(table(class(dirindex),
142 [ \dirindex_title,
143 \back
144 | \dirmembers(SubDirs, SortedFiles, Options)
145 ])).
146
147sort_files(name, Files, Files) :- !.
148sort_files(Order, Files, Sorted) :-
149 map_list_to_pairs(key_file(Order), Files, Pairs),
150 keysort(Pairs, SortedPairs),
151 pairs_values(SortedPairs, Sorted).
152
153key_file(name, File, Base) :-
154 file_base_name(File, Base).
155key_file(size, File, Size) :-
156 size_file(File, Size).
157key_file(time, File, Time) :-
158 time_file(File, Time).
159
160asc_desc(Files, Ordered, Options) :-
161 ( option(order(ascending), Options, ascending)
162 -> Ordered = Files
163 ; reverse(Files, Ordered)
164 ).
165
166dirindex_title -->
167 html(tr(class(dirindex_header),
168 [ th(class(icon), ''),
169 th(class(name), 'Name'),
170 th(class(modified), 'Last modified'),
171 th(class(size), 'Size')
172 ])).
173
174back -->
175 html(tr([ \icon_cell('back.png', '[UP]'),
176 td(class(name), \name_cell(.., [label('Up')])),
177 td(class(modified), -),
178 td(class(size), -)
179 ])).
180
181dirmembers(Dirs, Files, Options) -->
182 dir_rows(Dirs, odd, End),
183 file_rows(Files, End, _, Options).
184
185dir_rows([], OE, OE) --> [].
186dir_rows([H|T], OE0, OE) -->
187 dir_row(H, OE0),
188 { oe(OE0, OE1) },
189 dir_rows(T, OE1, OE).
190
191file_rows([], OE, OE, _) --> [].
192file_rows([H|T], OE0, OE, Options) -->
193 file_row(H, OE0, Options),
194 {oe(OE0, OE1)},
195 file_rows(T, OE1, OE, Options).
196
197oe(odd, even).
198oe(even, odd).
199
200dir_row(Dir, OE) -->
201 html(tr(class(OE),
202 [ \icon_cell('folder.png', '[DIR]'),
203 td(class(name), \name_cell(Dir, [])),
204 \modified_cell(Dir),
205 td(class(size), -)
206 ])).
207
208
209file_row(File, OE, Options) -->
210 { file_base_name(File, Name),
211 file_mime_type(File, MimeType),
212 mime_type_icon(MimeType, IconName)
213 },
214 html(tr(class(OE),
215 [ \icon_cell(IconName, '[FILE]'),
216 td(class(name), \name_cell(Name, Options)),
217 \modified_cell(File),
218 td(class(size), \size(File))
219 ])).
220
221icon_cell(IconName, Alt) -->
222 { http_absolute_location(icons(IconName), Icon, [])
223 },
224 html(td(class(icon), img([src(Icon), alt(Alt)]))).
225
226
227name_cell(File, Options) -->
228 { option(label(Label), Options),
229 !,
230 uri_encoded(path, File, Ref)
231 },
232 html(a(href(Ref), Label)).
233name_cell(File, Options) -->
234 { option(name(Name), Options) },
235 !,
236 call(Name, File).
237name_cell(File, _Options) -->
238 { file_base_name(File, Name),
239 uri_encoded(path, Name, Ref)
240 },
241 html(a(href(Ref), Name)).
242
243modified_cell(Name) -->
244 { time_file(Name, Stamp),
245 format_time(string(Date), '%+', Stamp)
246 },
247 html(td(class(modified), Date)).
248
249size(Name) -->
250 { size_file(Name, Size)
251 },
252 html('~D'-[Size]).
253
264
265mime_type_icon(Ext, Icon) :-
266 http:mime_type_icon(Ext, Icon),
267 !.
268mime_type_icon(_, 'generic.png').
269
277
278:- multifile
279 http:mime_type_icon/2. 280
281http:mime_type_icon(application/pdf, 'layout.png').
282http:mime_type_icon(text/csrc, 'c.png').
283http:mime_type_icon(application/'x-gzip', 'compressed.png').
284http:mime_type_icon(application/'x-gtar', 'compressed.png').
285http:mime_type_icon(application/zip, 'compressed.png').
286
287
288 291
292:- html_resource(http_dirindex,
293 [ virtual(true),
294 requires([ css('dirindex.css')
295 ])
296 ]).