sgfa 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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