88 :copyright: (c) 2013 by Hsiaoming Yang
99 :license: BSD, see LICENSE for more details.
1010"""
11-
11+ import datetime
12+ import hashlib
1213import os
14+ import stat
1315import time
1416import logging
1517from tornado import web
1618from tornado import ioloop
1719from tornado import escape
20+ from tornado .log import gen_log
1821from tornado .websocket import WebSocketHandler
1922from tornado .util import ObjectDict
2023
@@ -133,6 +136,80 @@ def on_message(self, message):
133136 LiveReloadHandler .waiters .add (self )
134137
135138
139+ class MtimeStaticFileHandler (web .StaticFileHandler ):
140+ _static_mtimes = {} # type: typing.Dict
141+
142+ @classmethod
143+ def get_content_modified_time (cls , abspath ):
144+ """Returns the time that ``abspath`` was last modified.
145+
146+ May be overridden in subclasses. Should return a `~datetime.datetime`
147+ object or None.
148+ """
149+ stat_result = os .stat (abspath )
150+ modified = datetime .datetime .utcfromtimestamp (
151+ stat_result [stat .ST_MTIME ])
152+ return modified
153+
154+ @classmethod
155+ def get_content_version (cls , abspath ):
156+ """Returns a version string for the resource at the given path.
157+
158+ This class method may be overridden by subclasses. The
159+ default implementation is a hash of the file's contents.
160+
161+ .. versionadded:: 3.1
162+ """
163+ data = cls .get_content (abspath )
164+ hasher = hashlib .md5 ()
165+
166+ mtime_data = format (cls .get_content_modified_time (abspath ), "%Y-%m-%d %H:%M:%S" )
167+
168+ hasher .update (mtime_data .encode ())
169+
170+ if isinstance (data , bytes ):
171+ hasher .update (data )
172+ else :
173+ for chunk in data :
174+ hasher .update (chunk )
175+ return hasher .hexdigest ()
176+
177+ @classmethod
178+ def _get_cached_version (cls , abs_path ):
179+ def _load_version (abs_path ):
180+ try :
181+ hsh = cls .get_content_version (abs_path )
182+ mtm = cls .get_content_modified_time (abs_path )
183+
184+ return mtm , hsh
185+ except Exception :
186+ gen_log .error ("Could not open static file %r" , abs_path )
187+ return None , None
188+
189+ with cls ._lock :
190+ hashes = cls ._static_hashes
191+ mtimes = cls ._static_mtimes
192+
193+ if abs_path not in hashes :
194+ mtm , hsh = _load_version (abs_path )
195+
196+ hashes [abs_path ] = mtm
197+ mtimes [abs_path ] = hsh
198+ else :
199+ hsh = hashes .get (abs_path )
200+ mtm = mtimes .get (abs_path )
201+
202+ if mtm != cls .get_content_modified_time (abs_path ):
203+ mtm , hsh = _load_version (abs_path )
204+
205+ hashes [abs_path ] = mtm
206+ mtimes [abs_path ] = hsh
207+
208+ if hsh :
209+ return hsh
210+ return None
211+
212+
136213class LiveReloadJSHandler (web .RequestHandler ):
137214
138215 def get (self ):
@@ -150,6 +227,6 @@ def get(self):
150227 self .write ('ok' )
151228
152229
153- class StaticFileHandler (web . StaticFileHandler ):
230+ class StaticFileHandler (MtimeStaticFileHandler ):
154231 def should_return_304 (self ):
155232 return False
0 commit comments