cache.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import web
  2. from time import gmtime
  3. from datetime import datetime, timedelta
  4. from os import mkdir
  5. from os.path import getmtime, exists
  6. from cPickle import load, dump
  7. ADMINISTRATION_FILE = 'administration.py'
  8. def to_dir(path):
  9. """
  10. Transform a path to a directory, that can be used as prefix before a
  11. filename.
  12. """
  13. if not path:
  14. return ''
  15. if path[-1] != '/':
  16. return path + '/'
  17. return path
  18. def assert_file_exists(path):
  19. if not exists(path):
  20. raise IOError('File "%s" does not exist.' % path)
  21. def seconds_to_datetime(seconds):
  22. return datetime(gmtime(seconds)[:7])
  23. class Cache:
  24. """
  25. A Cache instance represents a single file in the cache directory, which is
  26. a concatenation of the file list.
  27. """
  28. def __init__(self, root='', files=[], cached='cached',
  29. expires={'days': 365}):
  30. """
  31. The constructor takes the following arguments (all are optional):
  32. 1. The directory in which the cached files are located (empty by
  33. default).
  34. 2. An initial list of number of files to include in the cahce file.
  35. 3. The cached files directory (defaults to 'cached').
  36. 4. A dictionary containing arguments to the timedelta constructor, that
  37. indicates how long the cache object sould live.
  38. """
  39. self.root = to_dir(root)
  40. self.cached = to_dir(cached)
  41. self.files = map(lambda f: self.root + f, files)
  42. self.expires = expires
  43. self.assert_files_exist()
  44. def assert_files_exist(self):
  45. map(assert_file_exists, self.files)
  46. def add(self, path, absolute=False):
  47. """
  48. Add a file to the cache object. Requires the full path to the file,
  49. relative to the root directory. If the second argument is True, the
  50. root directory will not be prepended to the path.
  51. """
  52. if not absolute:
  53. path = self.root + path
  54. assert_file_exists(path)
  55. self.files.append(path)
  56. def remove(self, path, absolute=False):
  57. """
  58. Remove a file from the cache object. Requires the full path to the
  59. file, relative to the root directory. If the second argument is True,
  60. the root directory will not be prepended to the path.
  61. """
  62. if not absolute:
  63. path = self.root + path
  64. self.files.remove(path)
  65. def assert_modification_dates_exist(self):
  66. """
  67. Get the latest modification dates fo each file in the cache object.
  68. """
  69. if hasattr(self, 'modified'):
  70. return
  71. self.modified = {}
  72. for path in self.files:
  73. self.modified[path] = getmtime(path)
  74. def assert_cached_folder_exist(self):
  75. """
  76. Assert that the folder for cached files is created.
  77. """
  78. if not exists(self.cached):
  79. mkdir(self.cached)
  80. def save_administration(self):
  81. """
  82. Generate a Python file containing the modification dates of the cached
  83. file list.
  84. """
  85. self.assert_modification_dates_exist()
  86. self.assert_cached_folder_exist()
  87. f = open(self.cached + ADMINISTRATION_FILE, 'w')
  88. dump(self.modified, f)
  89. f.close()
  90. def last_modified(self):
  91. self.assert_modification_dates_exist()
  92. return self.modified[max(self.modified)]
  93. def etag(self):
  94. """
  95. Generate an Etag for the cache object, using the names of the files
  96. included and the latest modification date.
  97. """
  98. return self.filename() + str(self.last_modified())
  99. def filename(self):
  100. return ','.join(self.files)
  101. def concatenate(self):
  102. contents = ''
  103. for path in self.files:
  104. f = open(path, 'r')
  105. contents += f.read()
  106. f.close()
  107. return contents
  108. def output(self):
  109. """"""
  110. # Update cached file
  111. last_modified = self.last_modified()
  112. path = self.filename()
  113. server_modified = not exists(path) or getmtime(path) < last_modified
  114. if server_modified:
  115. content = self.concatenate()
  116. f = open(path, 'w')
  117. f.write(content)
  118. f.close()
  119. #try:
  120. web.http.modified(seconds_to_datetime(last_modified), self.etag())
  121. web.http.expires(timedelta(**self.expires))
  122. web.header('Cache-Control', 'private')
  123. if not server_modified:
  124. # Concatenated content has not been loaded yet, read the cached
  125. # file
  126. f = open(path, 'r')
  127. content = f.read()
  128. f.close()
  129. return content
  130. #except web.NotModified as e:
  131. # web.header('Status', e.message)