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