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