35
36:- module(download,
37 [ download_button/2 38 ]). 39:- use_module(library(pengines)). 40:- use_module(library(option)). 41:- use_module(library(settings)). 42:- use_module(library(apply)). 43:- use_module(library(http/mimetype)). 44:- use_module(library(http/http_dispatch)). 45:- use_module(library(http/http_parameters)). 46
56
57:- setting(keep_downloads_time, number, 86400,
58 "Seconds to keep a downloaded file"). 59
78
79download_button(Data, Options) :-
80 option(filename(FileName), Options, 'swish-download.dat'),
81 option(encoding(Enc), Options, utf8),
82 ( option(content_type(ContentType), Options)
83 -> true
84 ; file_mime_type(FileName, Major/Minor),
85 atomics_to_string([Major, Minor], /, ContentType0),
86 add_charset(Enc, ContentType0, ContentType)
87 ),
88 save_download_data(Data, UUID, Enc),
89 pengine_output(
90 json{action:downloadButton,
91 content_type:ContentType,
92 encoding: Enc,
93 uuid:UUID,
94 filename:FileName
95 }).
96
97add_charset(utf8, Enc0, Enc) :- !,
98 atom_concat(Enc0, '; charset=UTF-8', Enc).
99add_charset(_, Enc, Enc).
100
101
102 105
106:- http_handler(swish(download), download, [id(download), prefix, method(get)]). 107
111
112download(Request) :-
113 http_parameters(Request,
114 [ uuid(UUID, []),
115 content_type(Type, [])
116 ]),
117 download_file(UUID, File),
118 http_reply_file(File,
119 [ mime_type(Type),
120 unsafe(true)
121 ],
122 Request).
123
124
125 128
133
134save_download_data(Data, UUID, Encoding) :-
135 download_file(UUID, Path),
136 ensure_parents(Path),
137 setup_call_cleanup(
138 open(Path, write, Out, [encoding(Encoding)]),
139 write(Out, Data),
140 close(Out)),
141 prune_downloads.
142
143
152
153download_file(UUID, Path) :-
154 ( var(UUID)
155 -> uuid(UUID)
156 ; true
157 ),
158 variant_sha1(UUID, SHA1),
159 sub_atom(SHA1, 0, 2, _, Dir0),
160 sub_atom(SHA1, 2, 2, _, Dir1),
161 sub_atom(SHA1, 4, _, 0, File),
162 download_dir(Dir),
163 atomic_list_concat([Dir, Dir0, Dir1, File], /, Path).
164
165
169
170:- dynamic download_dir_cache/1. 171:- volatile download_dir_cache/1. 172
173download_dir(Dir) :-
174 download_dir_cache(Dir),
175 !.
176download_dir(Dir) :-
177 absolute_file_name(data(download), Dir,
178 [ file_type(directory),
179 access(write),
180 file_errors(fail)
181 ]),
182 !,
183 asserta(download_dir_cache(Dir)).
184download_dir(Dir) :-
185 absolute_file_name(data(download), Dir,
186 [ solutions(all)
187 ]),
188 catch(make_directory(Dir), error(_,_), fail),
189 !,
190 asserta(download_dir_cache(Dir)).
191
192ensure_parents(Path) :-
193 file_directory_name(Path, Dir1),
194 file_directory_name(Dir1, Dir0),
195 ensure_directory(Dir0),
196 ensure_directory(Dir1).
197
198ensure_directory(Dir) :-
199 exists_directory(Dir),
200 !.
201ensure_directory(Dir) :-
202 make_directory(Dir).
203
204
209
210:- dynamic pruned_at/1. 211:- volatile pruned_at/1. 212
213prune_downloads :-
214 E = error(_,_),
215 with_mutex(download,
216 catch(prune_downloads_sync, E,
217 print_message(warning, E))).
218
219prune_downloads_sync :-
220 pruned_at(Last),
221 setting(keep_downloads_time, Time),
222 get_time(Now),
223 Now < Last + Time/4,
224 !.
225prune_downloads_sync :-
226 thread_create(do_prune_downloads, _,
227 [ alias(prune_downloads),
228 detached(true)
229 ]),
230 get_time(Now),
231 retractall(pruned_at(_)),
232 asserta(pruned_at(Now)).
233
234do_prune_downloads :-
235 get_time(Now),
236 setting(keep_downloads_time, Time),
237 Before is Now - Time,
238 download_dir(Dir),
239 prune_dir(Dir, Before, false).
240
245
246prune_dir(Dir, Time, PruneDir) :-
247 directory_files(Dir, Files0),
248 exclude(reserved, Files0, Files),
249 exclude(clean_entry(Dir, Time), Files, Rest),
250 ( Rest == [],
251 PruneDir == true
252 -> E = error(_,_),
253 catch(delete_directory(Dir), E,
254 print_message(warning, E))
255 ; true
256 ).
257
258reserved(.).
259reserved(..).
260
264
265clean_entry(Dir, Time, File) :-
266 directory_file_path(Dir, File, Path),
267 ( exists_directory(Path)
268 -> prune_dir(Path, Time, true),
269 \+ exists_directory(Path)
270 ; time_file(Path, FTime),
271 FTime < Time
272 -> E = error(_,_),
273 catch(delete_file(Path), E,
274 ( print_message(warning, E),
275 fail
276 ))
277 )