comparison OrthancFramework/Resources/EmbedResources.py @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Resources/EmbedResources.py@78ee0155ec67
children bf7b9edf6b81
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
1 #!/usr/bin/python
2
3 # Orthanc - A Lightweight, RESTful DICOM Store
4 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
5 # Department, University Hospital of Liege, Belgium
6 # Copyright (C) 2017-2020 Osimis S.A., Belgium
7 #
8 # This program is free software: you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
12 #
13 # In addition, as a special exception, the copyright holders of this
14 # program give permission to link the code of its release with the
15 # OpenSSL project's "OpenSSL" library (or with modified versions of it
16 # that use the same license as the "OpenSSL" library), and distribute
17 # the linked executables. You must obey the GNU General Public License
18 # in all respects for all of the code used other than "OpenSSL". If you
19 # modify file(s) with this exception, you may extend this exception to
20 # your version of the file(s), but you are not obligated to do so. If
21 # you do not wish to do so, delete this exception statement from your
22 # version. If you delete this exception statement from all source files
23 # in the program, then also delete it here.
24 #
25 # This program is distributed in the hope that it will be useful, but
26 # WITHOUT ANY WARRANTY; without even the implied warranty of
27 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28 # General Public License for more details.
29 #
30 # You should have received a copy of the GNU General Public License
31 # along with this program. If not, see <http://www.gnu.org/licenses/>.
32
33
34 import sys
35 import os
36 import os.path
37 import pprint
38 import re
39
40 UPCASE_CHECK = True
41 USE_SYSTEM_EXCEPTION = False
42 EXCEPTION_CLASS = 'OrthancException'
43 OUT_OF_RANGE_EXCEPTION = '::Orthanc::OrthancException(::Orthanc::ErrorCode_ParameterOutOfRange)'
44 INEXISTENT_PATH_EXCEPTION = '::Orthanc::OrthancException(::Orthanc::ErrorCode_InexistentItem)'
45 NAMESPACE = 'Orthanc.EmbeddedResources'
46 FRAMEWORK_PATH = None
47
48 ARGS = []
49 for i in range(len(sys.argv)):
50 if not sys.argv[i].startswith('--'):
51 ARGS.append(sys.argv[i])
52 elif sys.argv[i].lower() == '--no-upcase-check':
53 UPCASE_CHECK = False
54 elif sys.argv[i].lower() == '--system-exception':
55 USE_SYSTEM_EXCEPTION = True
56 EXCEPTION_CLASS = '::std::runtime_error'
57 OUT_OF_RANGE_EXCEPTION = '%s("Parameter out of range")' % EXCEPTION_CLASS
58 INEXISTENT_PATH_EXCEPTION = '%s("Unknown path in a directory resource")' % EXCEPTION_CLASS
59 elif sys.argv[i].startswith('--namespace='):
60 NAMESPACE = sys.argv[i][sys.argv[i].find('=') + 1 : ]
61 elif sys.argv[i].startswith('--framework-path='):
62 FRAMEWORK_PATH = sys.argv[i][sys.argv[i].find('=') + 1 : ]
63
64 if len(ARGS) < 2 or len(ARGS) % 2 != 0:
65 print ('Usage:')
66 print ('python %s [--no-upcase-check] [--system-exception] [--namespace=<Namespace>] <TargetBaseFilename> [ <Name> <Source> ]*' % sys.argv[0])
67 exit(-1)
68
69 TARGET_BASE_FILENAME = ARGS[1]
70 SOURCES = ARGS[2:]
71
72 try:
73 # Make sure the destination directory exists
74 os.makedirs(os.path.normpath(os.path.join(TARGET_BASE_FILENAME, '..')))
75 except:
76 pass
77
78
79 #####################################################################
80 ## Read each resource file
81 #####################################################################
82
83 def CheckNoUpcase(s):
84 global UPCASE_CHECK
85 if (UPCASE_CHECK and
86 re.search('[A-Z]', s) != None):
87 raise Exception("Path in a directory with an upcase letter: %s" % s)
88
89 resources = {}
90
91 counter = 0
92 i = 0
93 while i < len(SOURCES):
94 resourceName = SOURCES[i].upper()
95 pathName = SOURCES[i + 1]
96
97 if not os.path.exists(pathName):
98 raise Exception("Non existing path: %s" % pathName)
99
100 if resourceName in resources:
101 raise Exception("Twice the same resource: " + resourceName)
102
103 if os.path.isdir(pathName):
104 # The resource is a directory: Recursively explore its files
105 content = {}
106 for root, dirs, files in os.walk(pathName):
107 dirs.sort()
108 files.sort()
109 base = os.path.relpath(root, pathName)
110
111 # Fix issue #24 (Build fails on OSX when directory has .DS_Store files):
112 # Ignore folders whose name starts with a dot (".")
113 if base.find('/.') != -1:
114 print('Ignoring folder: %s' % root)
115 continue
116
117 for f in files:
118 if f.find('~') == -1: # Ignore Emacs backup files
119 if base == '.':
120 r = f
121 else:
122 r = os.path.join(base, f)
123
124 CheckNoUpcase(r)
125 r = '/' + r.replace('\\', '/')
126 if r in content:
127 raise Exception("Twice the same filename (check case): " + r)
128
129 content[r] = {
130 'Filename' : os.path.join(root, f),
131 'Index' : counter
132 }
133 counter += 1
134
135 resources[resourceName] = {
136 'Type' : 'Directory',
137 'Files' : content
138 }
139
140 elif os.path.isfile(pathName):
141 resources[resourceName] = {
142 'Type' : 'File',
143 'Index' : counter,
144 'Filename' : pathName
145 }
146 counter += 1
147
148 else:
149 raise Exception("Not a regular file, nor a directory: " + pathName)
150
151 i += 2
152
153 #pprint.pprint(resources)
154
155
156 #####################################################################
157 ## Write .h header
158 #####################################################################
159
160 header = open(TARGET_BASE_FILENAME + '.h', 'w')
161
162 header.write("""
163 #pragma once
164
165 #include <string>
166 #include <list>
167
168 #if defined(_MSC_VER)
169 # pragma warning(disable: 4065) // "Switch statement contains 'default' but no 'case' labels"
170 #endif
171
172 """)
173
174
175 for ns in NAMESPACE.split('.'):
176 header.write('namespace %s {\n' % ns)
177
178
179 header.write("""
180 enum FileResourceId
181 {
182 """)
183
184 isFirst = True
185 for name in resources:
186 if resources[name]['Type'] == 'File':
187 if isFirst:
188 isFirst = False
189 else:
190 header.write(',\n')
191 header.write(' %s' % name)
192
193 header.write("""
194 };
195
196 enum DirectoryResourceId
197 {
198 """)
199
200 isFirst = True
201 for name in resources:
202 if resources[name]['Type'] == 'Directory':
203 if isFirst:
204 isFirst = False
205 else:
206 header.write(',\n')
207 header.write(' %s' % name)
208
209 header.write("""
210 };
211
212 const void* GetFileResourceBuffer(FileResourceId id);
213 size_t GetFileResourceSize(FileResourceId id);
214 void GetFileResource(std::string& result, FileResourceId id);
215
216 const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path);
217 size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path);
218 void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path);
219
220 void ListResources(std::list<std::string>& result, DirectoryResourceId id);
221
222 """)
223
224
225 for ns in NAMESPACE.split('.'):
226 header.write('}\n')
227
228 header.close()
229
230
231
232 #####################################################################
233 ## Write the resource content in the .cpp source
234 #####################################################################
235
236 PYTHON_MAJOR_VERSION = sys.version_info[0]
237
238 def WriteResource(cpp, item):
239 cpp.write(' static const uint8_t resource%dBuffer[] = {' % item['Index'])
240
241 f = open(item['Filename'], "rb")
242 content = f.read()
243 f.close()
244
245 # http://stackoverflow.com/a/1035360
246 pos = 0
247 buffer = [] # instead of appending a few bytes at a time to the cpp file,
248 # we first append each chunk to a list, join it and write it
249 # to the file. We've measured that it was 2-3 times faster in python3.
250 # Note that speed is important since if generation is too slow,
251 # cmake might try to compile the EmbeddedResources.cpp file while it is
252 # still being generated !
253 for b in content:
254 if PYTHON_MAJOR_VERSION == 2:
255 c = ord(b[0])
256 else:
257 c = b
258
259 if pos > 0:
260 buffer.append(",")
261
262 if (pos % 16) == 0:
263 buffer.append("\n")
264
265 if c < 0:
266 raise Exception("Internal error")
267
268 buffer.append("0x%02x" % c)
269 pos += 1
270
271 cpp.write("".join(buffer))
272 # Zero-size array are disallowed, so we put one single void character in it.
273 if pos == 0:
274 cpp.write(' 0')
275
276 cpp.write(' };\n')
277 cpp.write(' static const size_t resource%dSize = %d;\n' % (item['Index'], pos))
278
279
280 cpp = open(TARGET_BASE_FILENAME + '.cpp', 'w')
281
282 cpp.write('#include "%s.h"\n' % os.path.basename(TARGET_BASE_FILENAME))
283
284 if USE_SYSTEM_EXCEPTION:
285 cpp.write('#include <stdexcept>')
286 elif FRAMEWORK_PATH != None:
287 cpp.write('#include "%s/OrthancException.h"' % FRAMEWORK_PATH)
288 else:
289 cpp.write('#include <OrthancException.h>')
290
291 cpp.write("""
292 #include <stdint.h>
293 #include <string.h>
294
295 """)
296
297 for ns in NAMESPACE.split('.'):
298 cpp.write('namespace %s {\n' % ns)
299
300
301 for name in resources:
302 if resources[name]['Type'] == 'File':
303 WriteResource(cpp, resources[name])
304 else:
305 for f in resources[name]['Files']:
306 WriteResource(cpp, resources[name]['Files'][f])
307
308
309
310 #####################################################################
311 ## Write the accessors to the file resources in .cpp
312 #####################################################################
313
314 cpp.write("""
315 const void* GetFileResourceBuffer(FileResourceId id)
316 {
317 switch (id)
318 {
319 """)
320 for name in resources:
321 if resources[name]['Type'] == 'File':
322 cpp.write(' case %s:\n' % name)
323 cpp.write(' return resource%dBuffer;\n' % resources[name]['Index'])
324
325 cpp.write("""
326 default:
327 throw %s;
328 }
329 }
330
331 size_t GetFileResourceSize(FileResourceId id)
332 {
333 switch (id)
334 {
335 """ % OUT_OF_RANGE_EXCEPTION)
336
337 for name in resources:
338 if resources[name]['Type'] == 'File':
339 cpp.write(' case %s:\n' % name)
340 cpp.write(' return resource%dSize;\n' % resources[name]['Index'])
341
342 cpp.write("""
343 default:
344 throw %s;
345 }
346 }
347 """ % OUT_OF_RANGE_EXCEPTION)
348
349
350
351 #####################################################################
352 ## Write the accessors to the directory resources in .cpp
353 #####################################################################
354
355 cpp.write("""
356 const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path)
357 {
358 switch (id)
359 {
360 """)
361
362 for name in resources:
363 if resources[name]['Type'] == 'Directory':
364 cpp.write(' case %s:\n' % name)
365 isFirst = True
366 for path in resources[name]['Files']:
367 cpp.write(' if (!strcmp(path, "%s"))\n' % path)
368 cpp.write(' return resource%dBuffer;\n' % resources[name]['Files'][path]['Index'])
369 cpp.write(' throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION)
370
371 cpp.write(""" default:
372 throw %s;
373 }
374 }
375
376 size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path)
377 {
378 switch (id)
379 {
380 """ % OUT_OF_RANGE_EXCEPTION)
381
382 for name in resources:
383 if resources[name]['Type'] == 'Directory':
384 cpp.write(' case %s:\n' % name)
385 isFirst = True
386 for path in resources[name]['Files']:
387 cpp.write(' if (!strcmp(path, "%s"))\n' % path)
388 cpp.write(' return resource%dSize;\n' % resources[name]['Files'][path]['Index'])
389 cpp.write(' throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION)
390
391 cpp.write(""" default:
392 throw %s;
393 }
394 }
395 """ % OUT_OF_RANGE_EXCEPTION)
396
397
398
399
400 #####################################################################
401 ## List the resources in a directory
402 #####################################################################
403
404 cpp.write("""
405 void ListResources(std::list<std::string>& result, DirectoryResourceId id)
406 {
407 result.clear();
408
409 switch (id)
410 {
411 """)
412
413 for name in resources:
414 if resources[name]['Type'] == 'Directory':
415 cpp.write(' case %s:\n' % name)
416 for path in sorted(resources[name]['Files']):
417 cpp.write(' result.push_back("%s");\n' % path)
418 cpp.write(' break;\n\n')
419
420 cpp.write(""" default:
421 throw %s;
422 }
423 }
424 """ % OUT_OF_RANGE_EXCEPTION)
425
426
427
428
429 #####################################################################
430 ## Write the convenience wrappers in .cpp
431 #####################################################################
432
433 cpp.write("""
434 void GetFileResource(std::string& result, FileResourceId id)
435 {
436 size_t size = GetFileResourceSize(id);
437 result.resize(size);
438 if (size > 0)
439 memcpy(&result[0], GetFileResourceBuffer(id), size);
440 }
441
442 void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path)
443 {
444 size_t size = GetDirectoryResourceSize(id, path);
445 result.resize(size);
446 if (size > 0)
447 memcpy(&result[0], GetDirectoryResourceBuffer(id, path), size);
448 }
449 """)
450
451
452 for ns in NAMESPACE.split('.'):
453 cpp.write('}\n')
454
455 cpp.close()