gzip_cache.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. '''
  2. Copyright (c) 2012 Matt Layman
  3. Gzip cache
  4. ----------
  5. A plugin to create .gz cache files for optimization.
  6. '''
  7. import logging
  8. import os
  9. import zlib
  10. from pelican import signals
  11. logger = logging.getLogger(__name__)
  12. # A list of file types to exclude from possible compression
  13. EXCLUDE_TYPES = [
  14. # Compressed types
  15. '.bz2',
  16. '.gz',
  17. # Audio types
  18. '.aac',
  19. '.flac',
  20. '.mp3',
  21. '.wma',
  22. # Image types
  23. '.gif',
  24. '.jpg',
  25. '.jpeg',
  26. '.png',
  27. # Video types
  28. '.avi',
  29. '.mov',
  30. '.mp4',
  31. '.webm',
  32. # Internally-compressed fonts. gzip can often shave ~50 more bytes off,
  33. # but it's not worth it.
  34. '.woff',
  35. '.woff2',
  36. ]
  37. COMPRESSION_LEVEL = 9 # Best Compression
  38. """ According to zlib manual: 'Add 16 to
  39. windowBits to write a simple gzip header and trailer around the
  40. compressed data instead of a zlib wrapper. The gzip header will
  41. have no file name, no extra data, no comment, no modification
  42. time (set to zero), no header crc, and the operating system
  43. will be set to 255 (unknown)'
  44. """
  45. WBITS = zlib.MAX_WBITS | 16
  46. def create_gzip_cache(pelican):
  47. '''Create a gzip cache file for every file that a webserver would
  48. reasonably want to cache (e.g., text type files).
  49. :param pelican: The Pelican instance
  50. '''
  51. for dirpath, _, filenames in os.walk(pelican.settings['OUTPUT_PATH']):
  52. for name in filenames:
  53. if should_compress(name):
  54. filepath = os.path.join(dirpath, name)
  55. create_gzip_file(filepath, should_overwrite(pelican.settings))
  56. def should_compress(filename):
  57. '''Check if the filename is a type of file that should be compressed.
  58. :param filename: A file name to check against
  59. '''
  60. for extension in EXCLUDE_TYPES:
  61. if filename.endswith(extension):
  62. return False
  63. return True
  64. def should_overwrite(settings):
  65. '''Check if the gzipped files should overwrite the originals.
  66. :param settings: The pelican instance settings
  67. '''
  68. return settings.get('GZIP_CACHE_OVERWRITE', False)
  69. def create_gzip_file(filepath, overwrite):
  70. '''Create a gzipped file in the same directory with a filepath.gz name.
  71. :param filepath: A file to compress
  72. :param overwrite: Whether the original file should be overwritten
  73. '''
  74. compressed_path = filepath + '.gz'
  75. with open(filepath, 'rb') as uncompressed:
  76. gzip_compress_obj = zlib.compressobj(COMPRESSION_LEVEL,
  77. zlib.DEFLATED, WBITS)
  78. uncompressed_data = uncompressed.read()
  79. gzipped_data = gzip_compress_obj.compress(uncompressed_data)
  80. gzipped_data += gzip_compress_obj.flush()
  81. if len(gzipped_data) >= len(uncompressed_data):
  82. logger.debug('No improvement: %s' % filepath)
  83. return
  84. with open(compressed_path, 'wb') as compressed:
  85. logger.debug('Compressing: %s' % filepath)
  86. try:
  87. compressed.write(gzipped_data)
  88. except Exception as ex:
  89. logger.critical('Gzip compression failed: %s' % ex)
  90. if overwrite:
  91. logger.debug('Overwriting: %s with %s' % (filepath, compressed_path))
  92. os.remove(filepath)
  93. os.rename(compressed_path, filepath)
  94. def register():
  95. signals.finalized.connect(create_gzip_cache)