@@ -15,6 +15,8 @@ import (
15
15
"regexp"
16
16
"strconv"
17
17
"strings"
18
+ "sync"
19
+ "sync/atomic"
18
20
)
19
21
20
22
// ErrorCSSClass httml CSS error class name
@@ -23,16 +25,14 @@ var ErrorCSSClass = "hasError"
23
25
// TemplateLoader object handles loading and parsing of templates.
24
26
// Everything below the application's views directory is treated as a template.
25
27
type TemplateLoader struct {
26
- // Template data and implementation
27
- templatesAndEngineList []TemplateEngine
28
- // If an error was encountered parsing the templates, it is stored here.
29
- compileError * Error
30
28
// Paths to search for templates, in priority order.
31
29
paths []string
32
- // Map from template name to the path from whence it was loaded.
33
- TemplatePaths map [string ]string
34
- // A map of looked up template results
35
- TemplateMap map [string ]Template
30
+ // load version seed for templates
31
+ loadVersionSeed int
32
+ // A templateRuntime of looked up template results
33
+ runtimeLoader atomic.Value
34
+ // Lock to prevent concurrent map writes
35
+ templateMutex sync.Mutex
36
36
}
37
37
38
38
type Template interface {
@@ -56,32 +56,61 @@ func NewTemplateLoader(paths []string) *TemplateLoader {
56
56
return loader
57
57
}
58
58
59
- // Refresh method scans the views directory and parses all templates as Go Templates.
60
- // If a template fails to parse, the error is set on the loader.
61
- // (It's awkward to refresh a single Go Template)
59
+ // WatchDir returns true of directory doesn't start with . (dot)
60
+ // otherwise false
61
+ func (loader * TemplateLoader ) WatchDir (info os.FileInfo ) bool {
62
+ // Watch all directories, except the ones starting with a dot.
63
+ return ! strings .HasPrefix (info .Name (), "." )
64
+ }
65
+
66
+ // WatchFile returns true of file doesn't start with . (dot)
67
+ // otherwise false
68
+ func (loader * TemplateLoader ) WatchFile (basename string ) bool {
69
+ // Watch all files, except the ones starting with a dot.
70
+ return ! strings .HasPrefix (basename , "." )
71
+ }
72
+
73
+ // DEPRECATED Use TemplateLang, will be removed in future release
74
+ func (loader * TemplateLoader ) Template (name string ) (tmpl Template , err error ) {
75
+ runtimeLoader := loader .runtimeLoader .Load ().(* templateRuntime )
76
+ return runtimeLoader .TemplateLang (name , "" )
77
+ }
78
+
79
+ func (loader * TemplateLoader ) TemplateLang (name , lang string ) (tmpl Template , err error ) {
80
+ runtimeLoader := loader .runtimeLoader .Load ().(* templateRuntime )
81
+ return runtimeLoader .TemplateLang (name , lang )
82
+ }
83
+
62
84
// Refresh method scans the views directory and parses all templates as Go Templates.
63
85
// If a template fails to parse, the error is set on the loader.
64
86
// (It's awkward to refresh a single Go Template)
65
87
func (loader * TemplateLoader ) Refresh () (err * Error ) {
88
+ loader .templateMutex .Lock ()
89
+ defer loader .templateMutex .Unlock ()
90
+
91
+ loader .loadVersionSeed ++
92
+ runtimeLoader := & templateRuntime {loader : loader ,
93
+ version : loader .loadVersionSeed ,
94
+ templateMap : map [string ]Template {}}
95
+
66
96
TRACE .Printf ("Refreshing templates from %s" , loader .paths )
67
- if len (loader .templatesAndEngineList ) == 0 {
68
- if err = loader .InitializeEngines (GO_TEMPLATE ); err != nil {
69
- return
70
- }
97
+ if err = loader .initializeEngines (runtimeLoader , GO_TEMPLATE ); err != nil {
98
+ return
71
99
}
72
- for _ , engine := range loader .templatesAndEngineList {
100
+ for _ , engine := range runtimeLoader .templatesAndEngineList {
73
101
engine .Event (TEMPLATE_REFRESH_REQUESTED , nil )
74
102
}
75
103
fireEvent (TEMPLATE_REFRESH_REQUESTED , nil )
76
104
defer func () {
77
- for _ , engine := range loader .templatesAndEngineList {
105
+ for _ , engine := range runtimeLoader .templatesAndEngineList {
78
106
engine .Event (TEMPLATE_REFRESH_COMPLETED , nil )
79
107
}
80
108
fireEvent (TEMPLATE_REFRESH_COMPLETED , nil )
81
- // Reset the TemplateMap, we don't prepopulate the map because
82
- loader .TemplateMap = map [string ]Template {}
83
109
110
+ // Reset the runtimeLoader
111
+ loader .runtimeLoader .Store (runtimeLoader )
84
112
}()
113
+
85
114
// Resort the paths, make sure the revel path is the last path,
86
115
// so anything can override it
87
116
revelTemplatePath := filepath .Join (RevelPath , "templates" )
@@ -94,8 +123,8 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
94
123
}
95
124
TRACE .Printf ("Refreshing templates from %s" , loader .paths )
96
125
97
- loader .compileError = nil
98
- loader .TemplatePaths = map [string ]string {}
126
+ runtimeLoader .compileError = nil
127
+ runtimeLoader .TemplatePaths = map [string ]string {}
99
128
100
129
for _ , basePath := range loader .paths {
101
130
// Walk only returns an error if the template loader is completely unusable
@@ -133,14 +162,14 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
133
162
return nil
134
163
}
135
164
136
- fileBytes , err := loader .findAndAddTemplate (path , fullSrcDir , basePath )
165
+ fileBytes , err := runtimeLoader .findAndAddTemplate (path , fullSrcDir , basePath )
137
166
138
167
// Store / report the first error encountered.
139
- if err != nil && loader .compileError == nil {
140
- loader .compileError , _ = err .(* Error )
141
- if nil == loader .compileError {
168
+ if err != nil && runtimeLoader .compileError == nil {
169
+ runtimeLoader .compileError , _ = err .(* Error )
170
+ if nil == runtimeLoader .compileError {
142
171
_ , line , description := ParseTemplateError (err )
143
- loader .compileError = & Error {
172
+ runtimeLoader .compileError = & Error {
144
173
Title : "Template Compilation Error" ,
145
174
Path : path ,
146
175
Description : description ,
@@ -149,7 +178,7 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
149
178
}
150
179
}
151
180
ERROR .Printf ("Template compilation error (In %s around line %d):\n \t %s" ,
152
- path , loader .compileError .Line , err .Error ())
181
+ path , runtimeLoader .compileError .Line , err .Error ())
153
182
} else if nil != err { //&& strings.HasPrefix(templateName, "errors/") {
154
183
155
184
if compileError , ok := err .(* Error ); ok {
@@ -173,26 +202,40 @@ func (loader *TemplateLoader) Refresh() (err *Error) {
173
202
174
203
// If there was an error with the Funcs, set it and return immediately.
175
204
if funcErr != nil {
176
- loader .compileError = funcErr .(* Error )
177
- return loader .compileError
205
+ runtimeLoader .compileError = funcErr .(* Error )
206
+ return runtimeLoader .compileError
178
207
}
179
208
}
180
209
181
210
// Note: compileError may or may not be set.
182
- return loader .compileError
211
+ return runtimeLoader .compileError
212
+ }
213
+
214
+ type templateRuntime struct {
215
+ loader * TemplateLoader
216
+ // load version for templates
217
+ version int
218
+ // Template data and implementation
219
+ templatesAndEngineList []TemplateEngine
220
+ // If an error was encountered parsing the templates, it is stored here.
221
+ compileError * Error
222
+ // Map from template name to the path from whence it was loaded.
223
+ TemplatePaths map [string ]string
224
+ // A map of looked up template results
225
+ templateMap map [string ]Template
183
226
}
184
227
185
228
// Checks to see if template exists in templatePaths, if so it is skipped (templates are imported in order
186
229
// reads the template file into memory, replaces namespace keys with module (if found
187
- func (loader * TemplateLoader ) findAndAddTemplate (path , fullSrcDir , basePath string ) (fileBytes []byte , err error ) {
230
+ func (runtimeLoader * templateRuntime ) findAndAddTemplate (path , fullSrcDir , basePath string ) (fileBytes []byte , err error ) {
188
231
templateName := filepath .ToSlash (path [len (fullSrcDir )+ 1 :])
189
232
// Convert template names to use forward slashes, even on Windows.
190
233
if os .PathSeparator == '\\' {
191
234
templateName = strings .Replace (templateName , `\` , `/` , - 1 ) // `
192
235
}
193
236
194
237
// Check to see if template was found
195
- if place , found := loader .TemplatePaths [templateName ]; found {
238
+ if place , found := runtimeLoader .TemplatePaths [templateName ]; found {
196
239
TRACE .Println ("Not Loading, template is already exists: " , templateName , "\r \n \t old file:" ,
197
240
place , "\r \n \t new file:" , path )
198
241
return
@@ -205,25 +248,25 @@ func (loader *TemplateLoader) findAndAddTemplate(path, fullSrcDir, basePath stri
205
248
}
206
249
// Parse template file and replace the "_RNS_|" in the template with the module name
207
250
// allow for namespaces to be renamed "_RNS_(.*?)|"
208
- if module := ModuleFromPath (path , false );module != nil {
251
+ if module := ModuleFromPath (path , false ); module != nil {
209
252
fileBytes = namespaceReplace (fileBytes , module )
210
253
}
211
254
212
255
// if we have an engine picked for this template process it now
213
256
baseTemplate := NewBaseTemplate (templateName , path , basePath , fileBytes )
214
257
215
258
// Try to find a default engine for the file
216
- for _ , engine := range loader .templatesAndEngineList {
259
+ for _ , engine := range runtimeLoader .templatesAndEngineList {
217
260
if engine .Handles (baseTemplate ) {
218
- _ , err = loader .loadIntoEngine (engine , baseTemplate )
261
+ _ , err = runtimeLoader .loadIntoEngine (engine , baseTemplate )
219
262
return
220
263
}
221
264
}
222
265
223
266
// Try all engines available
224
267
var defaultError error
225
- for _ , engine := range loader .templatesAndEngineList {
226
- if loaded , loaderr := loader .loadIntoEngine (engine , baseTemplate ); loaded {
268
+ for _ , engine := range runtimeLoader .templatesAndEngineList {
269
+ if loaded , loaderr := runtimeLoader .loadIntoEngine (engine , baseTemplate ); loaded {
227
270
return
228
271
} else {
229
272
TRACE .Printf ("Engine '%s' unable to compile %s %s" , engine .Name (), path , loaderr )
@@ -244,7 +287,14 @@ func (loader *TemplateLoader) findAndAddTemplate(path, fullSrcDir, basePath stri
244
287
return
245
288
}
246
289
247
- func (loader * TemplateLoader ) loadIntoEngine (engine TemplateEngine , baseTemplate * TemplateView ) (loaded bool , err error ) {
290
+ func (runtimeLoader * templateRuntime ) loadIntoEngine (engine TemplateEngine , baseTemplate * TemplateView ) (loaded bool , err error ) {
291
+ if loadedTemplate , found := runtimeLoader .templateMap [baseTemplate .TemplateName ]; found {
292
+ // Duplicate template found in map
293
+ TRACE .Println ("template already exists in map: " , baseTemplate .TemplateName , " in engine " , engine .Name (), "\r \n \t old file:" ,
294
+ loadedTemplate .Location (), "\r \n \t new file:" , baseTemplate .FilePath )
295
+ return
296
+ }
297
+
248
298
if loadedTemplate := engine .Lookup (baseTemplate .TemplateName ); loadedTemplate != nil {
249
299
// Duplicate template found for engine
250
300
TRACE .Println ("template already exists: " , baseTemplate .TemplateName , " in engine " , engine .Name (), "\r \n \t old file:" ,
@@ -253,7 +303,10 @@ func (loader *TemplateLoader) loadIntoEngine(engine TemplateEngine, baseTemplate
253
303
return
254
304
}
255
305
if err = engine .ParseAndAdd (baseTemplate ); err == nil {
256
- loader .TemplatePaths [baseTemplate .TemplateName ] = baseTemplate .FilePath
306
+ if tmpl := engine .Lookup (baseTemplate .TemplateName ); tmpl != nil {
307
+ runtimeLoader .templateMap [baseTemplate .TemplateName ] = tmpl
308
+ }
309
+ runtimeLoader .TemplatePaths [baseTemplate .TemplateName ] = baseTemplate .FilePath
257
310
TRACE .Printf ("Engine '%s' compiled %s" , engine .Name (), baseTemplate .FilePath )
258
311
loaded = true
259
312
} else {
@@ -262,20 +315,6 @@ func (loader *TemplateLoader) loadIntoEngine(engine TemplateEngine, baseTemplate
262
315
return
263
316
}
264
317
265
- // WatchDir returns true of directory doesn't start with . (dot)
266
- // otherwise false
267
- func (loader * TemplateLoader ) WatchDir (info os.FileInfo ) bool {
268
- // Watch all directories, except the ones starting with a dot.
269
- return ! strings .HasPrefix (info .Name (), "." )
270
- }
271
-
272
- // WatchFile returns true of file doesn't start with . (dot)
273
- // otherwise false
274
- func (loader * TemplateLoader ) WatchFile (basename string ) bool {
275
- // Watch all files, except the ones starting with a dot.
276
- return ! strings .HasPrefix (basename , "." )
277
- }
278
-
279
318
// Parse the line, and description from an error message like:
280
319
// html/template:Application/Register.html:36: no such template "footer.html"
281
320
func ParseTemplateError (err error ) (templateName string , line int , description string ) {
@@ -300,53 +339,94 @@ func ParseTemplateError(err error) (templateName string, line int, description s
300
339
return templateName , line , description
301
340
}
302
341
303
- // DEPRECATED Use TemplateLang, will be removed in future release
304
- func (loader * TemplateLoader ) Template (name string ) (tmpl Template , err error ) {
305
- return loader .TemplateLang (name , "" )
306
- }
307
342
// Template returns the Template with the given name. The name is the template's path
308
343
// relative to a template loader root.
309
344
//
310
345
// An Error is returned if there was any problem with any of the templates. (In
311
346
// this case, if a template is returned, it may still be usable.)
312
- func (loader * TemplateLoader ) TemplateLang (name , lang string ) (tmpl Template , err error ) {
313
- if loader .compileError != nil {
314
- return nil , loader .compileError
315
- }
316
- // Attempt to load a localized template first.
317
- if lang != "" {
318
- // Look up and return the template.
319
- tmpl = loader .templateLoad (name + "." + lang )
320
- }
321
- // Return non localized version
322
- if tmpl == nil {
323
- tmpl = loader .templateLoad (name )
347
+ func (runtimeLoader * templateRuntime ) TemplateLang (name , lang string ) (tmpl Template , err error ) {
348
+ if runtimeLoader .compileError != nil {
349
+ return nil , runtimeLoader .compileError
324
350
}
325
351
326
- if tmpl == nil && err == nil {
352
+ // Fetch the template from the map
353
+ tmpl = runtimeLoader .templateLoad (name , lang )
354
+ if tmpl == nil {
327
355
err = fmt .Errorf ("Template %s not found." , name )
328
356
}
329
357
330
358
return
331
359
}
332
- func (loader * TemplateLoader ) templateLoad (name string ) (tmpl Template ) {
333
- if t ,found := loader .TemplateMap [name ];! found && t != nil {
334
- tmpl = t
360
+
361
+ // Load and also updates map if name is not found (to speed up next lookup)
362
+ func (runtimeLoader * templateRuntime ) templateLoad (name , lang string ) (tmpl Template ) {
363
+ langName := name
364
+ found := false
365
+ if lang != "" {
366
+ // Look up and return the template.
367
+ langName = name + "." + lang
368
+ tmpl , found = runtimeLoader .templateMap [langName ]
369
+ if found {
370
+ return
371
+ }
372
+ tmpl , found = runtimeLoader .templateMap [name ]
335
373
} else {
374
+ tmpl , found = runtimeLoader .templateMap [name ]
375
+ if found {
376
+ return
377
+ }
378
+ }
379
+
380
+ if ! found {
381
+ // Neither name is found
336
382
// Look up and return the template.
337
- for _ , engine := range loader .templatesAndEngineList {
383
+ for _ , engine := range runtimeLoader .templatesAndEngineList {
384
+ if tmpl = engine .Lookup (langName ); tmpl != nil {
385
+ found = true
386
+ break
387
+ }
338
388
if tmpl = engine .Lookup (name ); tmpl != nil {
339
- loader . TemplateMap [ name ] = tmpl
389
+ found = true
340
390
break
341
391
}
342
392
}
393
+ if ! found {
394
+ return
395
+ }
396
+ }
397
+
398
+ // If we found anything store it in the map, we need to copy so we do not
399
+ // run into concurrency issues
400
+ runtimeLoader .loader .templateMutex .Lock ()
401
+ defer runtimeLoader .loader .templateMutex .Unlock ()
402
+
403
+ // In case another thread has loaded the map, reload the atomic value and check
404
+ newRuntimeLoader := runtimeLoader .loader .runtimeLoader .Load ().(* templateRuntime )
405
+ if newRuntimeLoader .version != runtimeLoader .version {
406
+ return
407
+ }
408
+
409
+ newTemplateMap := map [string ]Template {}
410
+ for k , v := range newRuntimeLoader .templateMap {
411
+ newTemplateMap [k ] = v
412
+ }
413
+ newTemplateMap [langName ] = tmpl
414
+ if _ , found := newTemplateMap [name ]; ! found {
415
+ newTemplateMap [name ] = tmpl
343
416
}
417
+ runtimeCopy := & templateRuntime {}
418
+ * runtimeCopy = * newRuntimeLoader
419
+ runtimeCopy .templateMap = newTemplateMap
420
+
421
+ // Set the atomic value
422
+ runtimeLoader .loader .runtimeLoader .Store (runtimeCopy )
344
423
return
345
424
}
346
425
347
426
func (i * TemplateView ) Location () string {
348
427
return i .FilePath
349
428
}
429
+
350
430
func (i * TemplateView ) Content () (content []string ) {
351
431
if i .FileBytes != nil {
352
432
// Parse the bytes
@@ -358,7 +438,7 @@ func (i *TemplateView) Content() (content []string) {
358
438
}
359
439
return nil
360
440
}
441
+
361
442
func NewBaseTemplate (templateName , filePath , basePath string , fileBytes []byte ) * TemplateView {
362
443
return & TemplateView {TemplateName : templateName , FilePath : filePath , FileBytes : fileBytes , BasePath : basePath }
363
444
}
364
-
0 commit comments