sgfa 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,225 @@
1
+ #
2
+ # Simple Group of Filing Applications
3
+ # Web interface common utilities
4
+ #
5
+ # Copyright (C) 2015 by Graham A. Field.
6
+ #
7
+ # See LICENSE.txt for licensing information.
8
+ #
9
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
10
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+
12
+ require 'rack'
13
+ require 'time'
14
+
15
+ require_relative '../error'
16
+
17
+ module Sgfa
18
+
19
+
20
+ #####################################################################
21
+ # Web based interface to Sgfa
22
+ module Web
23
+
24
+
25
+ #####################################################################
26
+ # Shared utilities for Web classes to inherit
27
+ class Base
28
+
29
+ HtmlPage =
30
+ "<!DOCTYPE html>\n" +
31
+ "<html>\n" +
32
+ "<head>\n" +
33
+ "<title>%s</title>\n" +
34
+ "<link rel='stylesheet' type='text/css' href='%s'>\n" +
35
+ "</head>\n" +
36
+ "<body>\n" +
37
+ "<div id='sgfa_web'>\n" +
38
+ "<div class='title_bar'>" +
39
+ "<div class='title'>%s</div><div class='user'>%s</div></div>\n" +
40
+ "<div class='nav_bar'>\n%s</div>\n" +
41
+ "%s%s</div>\n" +
42
+ "</body>\n" +
43
+ "</html>\n"
44
+
45
+ HtmlMessage =
46
+ "<div class='message'>%s</div>\n"
47
+
48
+ HtmlBody =
49
+ "<div class='mainbody'>\n%s</div>\n"
50
+
51
+ #####################################
52
+ # Generate a response HTML page
53
+ def response(env)
54
+
55
+ # response code
56
+ code = case env['sgfa.status']
57
+ when :badreq; 400
58
+ when :ok; 200
59
+ when :notfound; 404
60
+ when :deny; 402
61
+ when :conflict; 409
62
+ when :servererror; 500
63
+ else; raise 'Response not set'
64
+ end
65
+
66
+ # returning a file
67
+ if env['sgfa.file']
68
+ return [code, env['sgfa.headers'], env['sgfa.file']]
69
+ end
70
+
71
+ # build the page and headers
72
+ body = HtmlBody % env['sgfa.html']
73
+ msg = env['sgfa.message'] ? (HtmlMessage % env['sgfa.message']) : ''
74
+ html = HtmlPage % [
75
+ env['sgfa.title'],
76
+ env['sgfa.css'],
77
+ env['sgfa.title'],
78
+ _escape_html(env['sgfa.user']),
79
+ env['sgfa.navbar'],
80
+ msg, body
81
+ ]
82
+ head = {
83
+ 'Content-Type' => 'text/html; charset=utf-8',
84
+ 'Content-Length' => html.bytesize.to_s,
85
+ }
86
+
87
+ return [code, head, [html]]
88
+ end # def response()
89
+
90
+
91
+ PageNav =
92
+ "<div class='pagenav'>%s</div>\n"
93
+
94
+ PageNavNums = [-1000, -500, -100, -50, -10, -5, -4, -3, -2, -1, 0,
95
+ 1, 2, 3, 4, 5, 10, 50, 100, 500, 1000]
96
+
97
+ ##########################################
98
+ # Generate page navigation
99
+ def _link_pages(cur, per, max, link, query)
100
+ pages = (per-1 + max) / per
101
+ txt = ''
102
+
103
+ if query
104
+ qs = '?'
105
+ query.each{ |nam, val| qs << '%s=%s' %[_escape(nam), _escape(val)] }
106
+ else
107
+ qs = ''
108
+ end
109
+
110
+ # previous and first
111
+ if cur > 1
112
+ txt << "<a href='%s/%d%s'>Prev</a> &mdash; " % [link, cur-1, qs]
113
+ txt << "<a href='%s/1%s'>First</a> &mdash; " % [link, qs]
114
+ else
115
+ txt << "Prev &mdash; First &mdash; "
116
+ end
117
+
118
+ # numbers
119
+ PageNavNums.each do |num|
120
+ val = cur + num
121
+ next if val < 1 || val > pages
122
+ if num == 0
123
+ txt << "%d " % cur
124
+ else
125
+ txt << "<a href='%s/%d%s'>%d</a> " % [link, val, qs, val]
126
+ end
127
+ end
128
+
129
+ # last & next
130
+ if cur < pages
131
+ txt << "&mdash; <a href='%s/%d%s'>Last</a> " % [link, pages, qs]
132
+ txt << "&mdash; <a href='%s/%s%s'>Next</a>" % [link, cur+1, qs]
133
+ else
134
+ txt << "&mdash; Last &mdash; Next"
135
+ end
136
+
137
+ return PageNav % txt
138
+ end # def _link_pages()
139
+
140
+
141
+ #####################################
142
+ # Generate a navbar
143
+ def _navbar(env, act, tabs, base)
144
+ txt = ''
145
+ tabs.each do |name, url|
146
+ cl = (name == act) ? 'active' : 'link'
147
+ if url
148
+ txt << "<div class='%s'><a href='%s/%s'>%s</a></div>\n" %
149
+ [cl, base, url, name]
150
+ else
151
+ txt << "<div class='other'>%s</div>\n" % name
152
+ end
153
+ end
154
+ return txt
155
+ end # def _navbar()
156
+
157
+
158
+ ##########################################
159
+ # Escape HTML
160
+ def _escape_html(txt)
161
+ Rack::Utils.escape_html(txt)
162
+ end # def _escape_html()
163
+
164
+
165
+ ##########################################
166
+ # Escape URL
167
+ def _escape(txt)
168
+ Rack::Utils.escape(txt)
169
+ end # def _escape()
170
+
171
+
172
+ ##########################################
173
+ # Unescape URL
174
+ def _escape_un(txt)
175
+ Rack::Utils.unescape(txt)
176
+ end # def _escape_un()
177
+
178
+
179
+ #####################################
180
+ # Create a binder transaction
181
+ def _trans(env)
182
+ tr = { :user => env['sgfa.user'], :groups => env['sgfa.groups'] }
183
+ tr[:jacket] = env['sgfa.jacket.name'] if env['sgfa.jacket.name']
184
+ return tr
185
+ end # def _trans()
186
+
187
+
188
+ end # class Base
189
+
190
+
191
+ #####################################################################
192
+ # Provide the contents of a file in chunks. Designed to work with Rack
193
+ # response.
194
+ class FileBody
195
+
196
+ # Size of the chunks provided by each
197
+ ReadChunk = 256 * 1024
198
+
199
+ # initialize new file response
200
+ def initialize(file)
201
+ @file = file
202
+ end # def initialize()
203
+
204
+ # provide the body of the file
205
+ def each
206
+ str = ''
207
+ while @file.read(ReadChunk, str)
208
+ yield str
209
+ end
210
+ end # def each
211
+
212
+ # close
213
+ def close
214
+ if @file.respond_to?(:close!)
215
+ @file.close!
216
+ else
217
+ @file.close
218
+ end
219
+ end # def close
220
+
221
+ end # class FileBody
222
+
223
+ end # module Web
224
+
225
+ end # module Sgfa