|
|
@@ -1,9 +1,11 @@
|
|
|
import web
|
|
|
+import cPickle as pickle
|
|
|
from time import gmtime
|
|
|
from datetime import datetime, timedelta
|
|
|
from os import mkdir
|
|
|
from os.path import getmtime, exists
|
|
|
-from cPickle import load, dump
|
|
|
+from hashlib import md5
|
|
|
+from copy import copy
|
|
|
|
|
|
|
|
|
ADMINISTRATION_FILE = 'administration.py'
|
|
|
@@ -29,7 +31,7 @@ def assert_file_exists(path):
|
|
|
|
|
|
|
|
|
def seconds_to_datetime(seconds):
|
|
|
- return datetime(gmtime(seconds)[:7])
|
|
|
+ return datetime(*gmtime(seconds)[:7])
|
|
|
|
|
|
|
|
|
class Cache:
|
|
|
@@ -59,6 +61,10 @@ class Cache:
|
|
|
def assert_files_exist(self):
|
|
|
map(assert_file_exists, self.files)
|
|
|
|
|
|
+ def __str__(self):
|
|
|
+ return '<Cache filename=%s files=[%s]>' \
|
|
|
+ % (self.filename(), ','.join(self.files))
|
|
|
+
|
|
|
def add(self, path, absolute=False):
|
|
|
"""
|
|
|
Add a file to the cache object. Requires the full path to the file,
|
|
|
@@ -101,18 +107,35 @@ class Cache:
|
|
|
if not exists(self.cached):
|
|
|
mkdir(self.cached)
|
|
|
|
|
|
- def save_administration(self):
|
|
|
+ def save_administration(self, old_admin={}):
|
|
|
"""
|
|
|
Generate a Python file containing the modification dates of the cached
|
|
|
- file list.
|
|
|
+ file list. The old_admin paramter can contain additional files which
|
|
|
+ are not in this cache object, but do need to be kept in the
|
|
|
+ administration for other cache objects.
|
|
|
"""
|
|
|
self.assert_modification_dates_exist()
|
|
|
self.assert_cached_folder_exist()
|
|
|
|
|
|
+ admin = copy(old_admin)
|
|
|
+ admin.update(self.modified)
|
|
|
+
|
|
|
f = open(self.cached + ADMINISTRATION_FILE, 'w')
|
|
|
- dump(self.modified, f)
|
|
|
+ pickle.dump(self.modified, f)
|
|
|
+ f.close()
|
|
|
+
|
|
|
+ def load_administration(self):
|
|
|
+ path = self.cached + ADMINISTRATION_FILE
|
|
|
+
|
|
|
+ if not exists(path):
|
|
|
+ return {}
|
|
|
+
|
|
|
+ f = open(path, 'r')
|
|
|
+ modified = pickle.load(f)
|
|
|
f.close()
|
|
|
|
|
|
+ return modified
|
|
|
+
|
|
|
def last_modified(self):
|
|
|
self.assert_modification_dates_exist()
|
|
|
|
|
|
@@ -123,10 +146,16 @@ class Cache:
|
|
|
Generate an Etag for the cache object, using the names of the files
|
|
|
included and the latest modification date.
|
|
|
"""
|
|
|
- return self.filename() + str(self.last_modified())
|
|
|
+ h = md5()
|
|
|
+ h.update(','.join(self.files) + str(self.last_modified()))
|
|
|
+
|
|
|
+ return h.hexdigest()
|
|
|
|
|
|
def filename(self):
|
|
|
- return ','.join(self.files)
|
|
|
+ h = md5()
|
|
|
+ h.update(','.join(self.files))
|
|
|
+
|
|
|
+ return self.cached + h.hexdigest()
|
|
|
|
|
|
def concatenate(self):
|
|
|
contents = ''
|
|
|
@@ -143,27 +172,45 @@ class Cache:
|
|
|
# Update cached file
|
|
|
last_modified = self.last_modified()
|
|
|
path = self.filename()
|
|
|
- server_modified = not exists(path) or getmtime(path) < last_modified
|
|
|
+ admin = self.load_administration()
|
|
|
+
|
|
|
+ if not exists(path):
|
|
|
+ web.debug('Cached file "%s" does not exist yet, generating it...')
|
|
|
+ server_modified = True
|
|
|
+ else:
|
|
|
+ server_modified = False
|
|
|
+
|
|
|
+ for f_path, f_modified in self.modified.iteritems():
|
|
|
+ if f_path not in admin:
|
|
|
+ web.debug('File "%s" has been added.' % f_path)
|
|
|
+ server_modified = True
|
|
|
+ elif f_modified > admin[f_path]:
|
|
|
+ web.debug('File "%s" has been updated.' % f_path)
|
|
|
+ server_modified = True
|
|
|
|
|
|
if server_modified:
|
|
|
- content = self.concatenate()
|
|
|
+ self.save_administration(admin)
|
|
|
|
|
|
+ content = self.concatenate()
|
|
|
f = open(path, 'w')
|
|
|
f.write(content)
|
|
|
f.close()
|
|
|
|
|
|
- #try:
|
|
|
- web.http.modified(seconds_to_datetime(last_modified), self.etag())
|
|
|
- web.http.expires(timedelta(**self.expires))
|
|
|
- web.header('Cache-Control', 'private')
|
|
|
-
|
|
|
- if not server_modified:
|
|
|
- # Concatenated content has not been loaded yet, read the cached
|
|
|
- # file
|
|
|
- f = open(path, 'r')
|
|
|
- content = f.read()
|
|
|
- f.close()
|
|
|
-
|
|
|
- return content
|
|
|
- #except web.NotModified as e:
|
|
|
- # web.header('Status', e.message)
|
|
|
+ try:
|
|
|
+ web.http.modified(seconds_to_datetime(last_modified), self.etag())
|
|
|
+ web.http.expires(timedelta(**self.expires))
|
|
|
+ web.header('Cache-Control', 'private')
|
|
|
+
|
|
|
+ if not server_modified:
|
|
|
+ # Concatenated content has not been loaded yet, read the cached
|
|
|
+ # file
|
|
|
+ web.debug('Cached file "%s" already exists, sending content...')
|
|
|
+ f = open(path, 'r')
|
|
|
+ content = f.read()
|
|
|
+ f.close()
|
|
|
+
|
|
|
+ return content
|
|
|
+ except web.NotModified as e:
|
|
|
+ web.debug('Cached file "%s" not modified, setting 304 header...' \
|
|
|
+ % path)
|
|
|
+ raise e
|