v2 / vlib / x / templating / dtm / dynamic_template_manager.v
1152 lines · 1079 sloc · 47.5 KB · 3eff1b83cf719199d0ff6f63524da63c9294ffd5
Raw
1@[deprecated: 'use x.templating.dtm2 for new code']
2@[deprecated_after: '2999-01-01']
3module dtm
4
5import os
6import crypto.md5
7import hash.fnv1a
8import time
9import strings
10import x.templating.dtm2
11
12// These are all the types of dynamic values that the DTM allows to be returned in the context of a map
13
14@[deprecated: 'use string placeholders with x.templating.dtm2.RenderParams for new code']
15@[deprecated_after: '2999-01-01']
16pub type DtmMultiTypeMap = f32 | f64 | i16 | i64 | i8 | int | string | u16 | u32 | u64 | u8
17
18// type MiddlewareFn = fn (mut Context, string) bool
19
20// cache_delay_expiration_at_min is the minimum setting for cache expiration delay, fixed at 5 minutes (measured in seconds).
21
22@[deprecated: 'use x.templating.dtm2 for new code']
23@[deprecated_after: '2999-01-01']
24pub const cache_delay_expiration_at_min = 300
25// cache_delay_expiration_at_max maximal is the maximal setting for cache expiration delay, fixed at 1 year (measured in seconds).
26
27@[deprecated: 'use x.templating.dtm2 for new code']
28@[deprecated_after: '2999-01-01']
29pub const cache_delay_expiration_at_max = 31536000
30// cache_delay_expiration_by_default is the default setting for cache expiration delay, fixed at 1 day (measured in seconds).
31
32@[deprecated: 'use x.templating.dtm2 for new code']
33@[deprecated_after: '2999-01-01']
34pub const cache_delay_expiration_by_default = 86400
35// Setting channel capacity of the legacy cache handler compatibility channel.
36const cache_handler_channel_cap = 200
37// Setting the maximum data size (500 KB) to be stored at memory mode. If this limit is exceeded, rendered cache switches to disk mode.
38const max_size_data_in_memory = 500
39// Defines the maximum character length for placeholder keys.
40const max_placeholders_key_size = 50
41// Sets the maximum character length for placeholder values.
42const max_placeholders_value_size = 3000
43// Internal DTM operations use microseconds to keep cache generation timestamps monotonic.
44const convert_seconds = i64(1000000)
45const converted_cache_delay_expiration_at_min = i64(cache_delay_expiration_at_min) * convert_seconds
46const converted_cache_delay_expiration_at_max = i64(cache_delay_expiration_at_max) * convert_seconds
47
48const internat_server_error = 'Internal Server Error'
49
50const message_signature = '[Dynamic Template Manager]'
51const message_signature_info = '[Dynamic Template Manager] Info :'
52const message_signature_error = '[Dynamic Template Manager] Error :'
53const message_signature_warn = '[Dynamic Template Manager] Warning :'
54
55@[deprecated: 'use x.templating.dtm2 for new code']
56@[deprecated_after: '2999-01-01']
57pub enum CacheStorageMode {
58 memory
59 disk
60}
61
62enum CacheRequest {
63 new
64 update
65 exp_update
66 cached
67 delete
68}
69
70enum TemplateType {
71 html
72 text
73}
74
75@[deprecated: 'use x.templating.dtm2.Manager for new code']
76@[deprecated_after: '2999-01-01']
77@[heap]
78pub struct DynamicTemplateManager {
79mut:
80 // Determines if the DTM initialization was performed successfully and if DTM is usable
81 dtm_init_is_ok bool
82 // Store the path to the cache directory.
83 template_cache_folder string
84 // Store the path to the HTML templates directory.
85 template_folder string
86 // cache database
87 template_caches shared []TemplateCache = []TemplateCache{}
88 // Fast lookup from a full template path hash to the active rendered-cache id.
89 template_cache_ids_by_path_hash map[u64]int = map[u64]int{}
90 // counter for each individual TemplateCache created/updated
91 id_counter int = 1
92 ch_cache_handler chan TemplateCache = chan TemplateCache{cap: cache_handler_channel_cap}
93 // 'id_to_handlered' is preserved for legacy callers that route cache update/delete work through the old API.
94 id_to_handlered int
95 close_cache_handler bool
96 // Initialisation params options for these two (Compress_html and active_cache_server)
97 compress_html bool = true
98 active_cache_server bool = true
99 // Initialisation of max data size in memory storage
100 max_size_data_in_memory int = max_size_data_in_memory
101 // This array is designed to store a control process that checks whether cached data is currently in use while simultaneously handling expiration.
102 // This allows for the harmonious management of both aspects and facilitates the necessary actions.
103 nbr_of_remaining_template_request shared []RemainingTemplateRequest = []RemainingTemplateRequest{}
104 // Dtm clock
105 c_time i64
106 ch_stop_dtm_clock chan bool = chan bool{cap: 5}
107 // Store small information about already cached pages to improve the verification speed of the check_tmpl_and_placeholders_size function.
108 html_file_info shared map[string]HtmlFileInfo = map[string]HtmlFileInfo{}
109 // Indicates whether the cache file storage directory is located in a temporary OS area
110 cache_folder_is_temporary_storage bool
111 // Handler for legacy cache-handler compatibility work.
112 threads_handler []thread = []thread{}
113 // This channel is used by legacy tests/callers to observe cache-handler completion.
114 is_ready chan bool = chan bool{cap: 5}
115 // If synchronization times out in legacy cache-handler tests, they set this flag.
116 abort_test bool
117 // Runtime rendering engine used by the compatibility layer. It caches parsed
118 // template structures only, never rendered HTML responses.
119 render_engine &dtm2.Manager = unsafe { nil }
120}
121
122// Represent individual template cache in database memory.
123@[noinit]
124struct TemplateCache {
125mut:
126 id int
127 name string
128 // Previous cache id replaced by this cache request, only set for update requests.
129 old_id int
130 // 'path' field contains the full path, name and file extension of targeted HTML template.
131 path string
132 // Checksum of the cached template, which is constructed as follows: the complete HTML content plus its full file path and the timestamp of the cache generation.
133 // This approach is utilized to create a unique identifier."
134 checksum string
135 // The checksum for the dynamic content of an HTML Template is constructed as follows: all dynamic values are concatenated into a single string,
136 // and the checksum is then calculated based on this string.
137 // Given that the value of this checksum is specific to its parent template, there is no issue of collision, and it does not need to be unique.
138 content_checksum string
139 // The timestamp of the last modification of the file (HTML template)
140 last_template_mod i64
141 // Timestamp of cache generation.
142 generate_at i64
143 // Timestamp of cache expiration define by user.
144 cache_delay_expiration i64
145 // Rendered HTML kept directly in memory when the cache entry uses memory storage.
146 html_data string
147 cache_request CacheRequest
148 cache_storage_mode CacheStorageMode
149 // This field is special as it determines if a cache is obsolete but still in use, allowing the system to redirect to the updated cache.
150 // This enables the eventual deletion of the obsolete cache without causing issues for requests utilizing the cache.
151 id_redirection int
152 // Contains the full path to a cache stored on disk.
153 cache_full_path_name string
154}
155
156// Represents controls stored in the 'nbr_of_remaining_template_request' of the DTM.
157// It is used to monitor whether a rendered cache entry is in use and to manage
158// deferred deletion when a cache entry is replaced.
159@[noinit]
160struct RemainingTemplateRequest {
161 // The id is similar to the one it represents in template_caches.
162 id int
163mut:
164 // This value determines the number of ongoing requests using the cache specified by the ID.
165 nbr_of_remaining_request int
166 // The cache has become obsolete, and permission to dispose of it is granted to the cache lifecycle.
167 need_to_delete bool
168 // If an obsolete cache cannot be destroyed because it is still in use, this
169 // boolean allows deletion to be retried when the active request finishes.
170 need_to_send_delete_request bool
171}
172
173@[noinit]
174struct HtmlFileInfo {
175 file_full_path string
176 file_name string
177 file_type TemplateType
178}
179
180struct PlaceholderChecksumValue {
181 key string
182 value string
183}
184
185// TemplateCacheParams are used to specify cache expiration delay and provide placeholder data for substitution in templates.
186@[deprecated: 'use x.templating.dtm2.RenderParams for new code']
187@[deprecated_after: '2999-01-01']
188@[params]
189pub struct TemplateCacheParams {
190pub:
191 placeholders &map[string]DtmMultiTypeMap = &map[string]DtmMultiTypeMap{}
192 cache_delay_expiration i64 = cache_delay_expiration_by_default
193}
194
195// DynamicTemplateManagerInitialisationParams is used with 'initialize' function. (See below at initialize section)
196@[deprecated: 'use x.templating.dtm2.ManagerParams for new code']
197@[deprecated_after: '2999-01-01']
198@[params]
199pub struct DynamicTemplateManagerInitialisationParams {
200pub:
201 def_cache_path string
202 compress_html bool = true
203 active_cache_server bool = true
204 max_size_data_in_mem int = max_size_data_in_memory
205 // Used by DTM internal tests to override the cache directory.
206 test_cache_dir string
207 // Used by DTM internal tests to override the templates directory.
208 test_template_dir string
209}
210
211// initialize create and init the 'DynamicTemplateManager' with the storage mode, cache/templates path folders.
212// A cache directory can be created by the user for storage. If it is not defined or encounters issues such as permission problems,
213// the DTM will attempt to create it in the OS's temporary area. If this proves impossible, the cache system will be deactivated and the user will be informed if cache system was required.
214// Initialisation params are :
215// - def_cache_path 'type string' User can define the path of cache folder.
216// - max_size_data_in_mem 'type int' Maximum size of data allowed in memory for caching. The value must be specified in kilobytes. ( Default is: 500KB / Limit max is : 500KB)
217// - compress_html: 'type bool' Light compress of the HTML output. ( default is true )
218// - active_cache_server: 'type bool' Activate or not the template cache system. ( default is true )
219// - test_cache_dir: 'type string' Used by DTM internal tests to override the cache directory.
220// - test_template_dir: 'type string' Used by DTM internal tests to override the templates directory.
221@[deprecated: 'use x.templating.dtm2.initialize for new code']
222@[deprecated_after: '2999-01-01']
223pub fn initialize(dtm_init_params DynamicTemplateManagerInitialisationParams) &DynamicTemplateManager {
224 mut dir_path := dtm_init_params.def_cache_path
225 mut dir_html_path := os.join_path('${os.dir(os.executable())}/templates')
226 mut max_size_memory := 0
227 mut active_rendered_cache := dtm_init_params.active_cache_server
228 mut system_ready := true
229 mut cache_temporary_bool := false
230
231 if dtm_init_params.test_cache_dir != '' {
232 dir_path = dtm_init_params.test_cache_dir
233 }
234 if dtm_init_params.test_template_dir != '' {
235 dir_html_path = dtm_init_params.test_template_dir
236 }
237 if active_rendered_cache {
238 // Control if cache folder created by user exist
239 if dir_path != '' && os.exists(dir_path) && os.is_dir(dir_path) {
240 // WARNING: When setting the directory for caching files and for testing purposes,
241 // 'check_and_clear_cache_files' function will delete all "*.cache" or "*.tmp" files inside the specified cache directory. Ensure that
242 // directory used for the cache does not contain any important files.
243 check_and_clear_cache_files(dir_path) or {
244 system_ready = false
245 eprintln(err.msg())
246 }
247 } else {
248 // If the cache folder is not found or problems, the dtm will attempt to create it in the temporary OS area.
249 dir_path = os.join_path(os.temp_dir(), 'vcache_dtm')
250 if !os.exists(dir_path) || !os.is_dir(dir_path) {
251 os.mkdir(dir_path) or {
252 active_rendered_cache = false
253 eprintln(err.msg())
254 }
255 }
256 if active_rendered_cache {
257 check_and_clear_cache_files(dir_path) or {
258 active_rendered_cache = false
259 eprintln(err.msg())
260 }
261 }
262 // If it is impossible to use a cache directory, the cache system is deactivated, and the user is warned."
263 if !active_rendered_cache {
264 eprintln('${message_signature_warn} The cache storage directory does not exist or has a problem and it was also not possible to use a folder suitable for temporary storage. Therefore, the cache system will be disabled. It is recommended to address the aforementioned issues to utilize the cache system.')
265 } else {
266 cache_temporary_bool = true
267 }
268 }
269 }
270 // Control if 'templates' folder exist in the root project
271 if !os.exists(dir_html_path) && !os.is_dir(dir_html_path) {
272 system_ready = false
273 eprintln('${message_signature_error} The templates directory at the project root does not exist. Please create a "templates" directory at the root of your project with appropriate read permissions. This is a mandatory step for using the Dynamic Template Manager (DTM). Current path attempted for create the templates folder: "${dir_html_path}"')
274 }
275 // Validates the 'max_size_data_in_mem' setting in 'dtm_init_params'. If it's within the valid range, it's applied; otherwise, default value is used.
276 if dtm_init_params.max_size_data_in_mem <= max_size_data_in_memory
277 && dtm_init_params.max_size_data_in_mem >= 0 {
278 max_size_memory = dtm_init_params.max_size_data_in_mem
279 } else {
280 max_size_memory = max_size_data_in_memory
281 mut type_error := 'exceeds'
282 if dtm_init_params.max_size_data_in_mem < 0 {
283 type_error = 'is invalid for define'
284 }
285 eprintln('${message_signature_info} The value "${dtm_init_params.max_size_data_in_mem}KB" ${type_error} the memory storage limit. It will not be considered, and the limit will be set to ${max_size_data_in_memory}KB.')
286 }
287
288 mut dtmi := &DynamicTemplateManager{
289 template_cache_folder: dir_path.clone()
290 template_folder: dir_html_path.clone()
291 template_cache_ids_by_path_hash: map[u64]int{}
292 max_size_data_in_memory: max_size_memory
293 compress_html: dtm_init_params.compress_html
294 active_cache_server: active_rendered_cache
295 c_time: get_current_unix_micro_timestamp()
296 dtm_init_is_ok: system_ready
297 cache_folder_is_temporary_storage: cache_temporary_bool
298 render_engine: new_dtm2_render_engine(dir_html_path,
299 dtm_init_params.compress_html)
300 }
301 if system_ready {
302 if active_rendered_cache {
303 dtmi.threads_handler << spawn dtmi.cache_handler()
304 }
305 // Rendered-cache work is processed synchronously. This keeps prod/Boehm behavior
306 // deterministic while preserving DTM's dynamic rendered-cache feature.
307 println('${message_signature} Dynamic Template Manager activated')
308 } else {
309 eprintln('${message_signature_error} Unable to use the Dynamic Template Manager, please refer to the above errors and correct them.')
310 }
311
312 return dtmi
313}
314
315/*
316fn init_cache_block_middleware(cache_dir string, mut dtm &DynamicTemplateManager) {
317 // Fonction locale correspondant à la signature MiddlewareFn
318 dtm.cache_block_middleware = fn (mut ctx Context, cache_dir string) bool {
319 request_path := ctx.req.url
320 if request_path.contains(cache_dir) {
321 ctx.text('Access to the cache directory is not allowed.')
322 return false
323 }
324 return true
325 }
326}
327*/
328
329// expand manages the cache and returns generated HTML.
330// Requires an initialization via 'initialize' to running.
331// To use this function, HTML templates must be located in the 'templates' directory at the project's root.
332// However, it allows the use of subfolder paths within the 'templates' directory,
333// enabling users to structure their templates in a way that best suits their project's organization.
334@[deprecated: 'use x.templating.dtm2.Manager.expand for new code']
335@[deprecated_after: '2999-01-01']
336pub fn (mut tm DynamicTemplateManager) expand(tmpl_path string, tmpl_var TemplateCacheParams) string {
337 if !tm.dtm_init_is_ok {
338 tm.disable_cache()
339 eprintln('${message_signature_error} The initialization phase of DTM has failed. Therefore, you cannot use it. Please address the errors and then restart the dtm server.')
340 return internat_server_error
341 }
342 return tm.expand_with_dtm2_render_engine(tmpl_path, tmpl_var)
343}
344
345// disable_cache disables future rendered-cache storage for this manager.
346@[deprecated: 'legacy DTM compatibility only; use x.templating.dtm2 for new code']
347@[deprecated_after: '2999-01-01']
348pub fn (mut tm DynamicTemplateManager) disable_cache() {
349 tm.stop_legacy_cache_handler()
350}
351
352// stop_cache_handler is kept for backward compatibility with the former cache-server
353// API. Rendered-cache work is now synchronous, so stopping it simply disables future
354// rendered-cache storage for this manager.
355@[deprecated: 'use disable_cache while maintaining legacy DTM code; use x.templating.dtm2 for new code']
356@[deprecated_after: '2999-01-01']
357pub fn (mut tm DynamicTemplateManager) stop_cache_handler() {
358 tm.stop_legacy_cache_handler()
359}
360
361fn (mut tm DynamicTemplateManager) stop_legacy_cache_handler() {
362 if !tm.active_cache_server {
363 return
364 }
365 tm.active_cache_server = false
366 tm.close_cache_handler = true
367 tm.ch_cache_handler <- TemplateCache{
368 id: 0
369 }
370 if tm.threads_handler.len > 0 {
371 tm.threads_handler.wait()
372 tm.threads_handler = []thread{}
373 }
374}
375
376// check_and_clear_cache_files is used exclusively during the initialization of the DTM (Dynamic Template Manager).
377// Its primary purpose is to ensure a clean starting environment by clearing all files
378// within the designated cache directory. It iterates through the directory, listing all files,
379// and systematically removes each file found, ensuring that no residual cache data persists
380// from previous executions. Additionally, this function also tests the read and write permissions
381// of the cache directory to ensure that the application has the necessary access to properly manage the cache files.
382// WARNING: When setting the directory for caching files and for testing purposes,
383// this function will delete all "*.cache" or "*.tmp" files inside the cache directory in the project root's. Ensure that
384// directory used for the cache does not contain any important files.
385fn check_and_clear_cache_files(c_folder string) ! {
386 // println('${message_signature} WARNING! DTM needs to perform some file tests in the cache directory. This operation will erase all "*.cache" or "*.tmp" files content in the folder : "${tm.template_cache_folder}"')
387 // println('Do you want to continue the operation? (yes/no)')
388 // mut user_response := os.input('>').to_lower()
389 // if user_response != 'yes' && user_response != 'y' {
390 // return error('${message_signature_error} Operation cancelled by the user. DTM initialization failed.')
391 // } else {
392 file_p := os.join_path(c_folder, 'test.tmp')
393 // Create a text file for test permission access
394 mut f := os.create(file_p) or {
395 return error('${message_signature_error} Files are not writable. Test fail, DTM initialization failed : ${err.msg()}')
396 }
397 f.close()
398 // Read the previous text file for test permission access
399 os.read_file(file_p) or {
400 return error('${message_signature_error} Files are not readable. Test fail, DTM initialization failed : ${err.msg()}')
401 }
402 // List all files in the cache folder
403 files_list := os.ls(c_folder) or {
404 return error('${message_signature_error} While listing the cache directorie files, DTM initialization failed : ${err.msg()}')
405 }
406 // Delete one by one "*.cache" or "*.tmp" files in the previous file list
407 for file in files_list {
408 file_path := os.join_path(c_folder, file)
409 file_extension := os.file_ext(file_path).to_lower()
410 if file_extension in ['.tmp', '.cache'] {
411 os.rm(file_path) or {
412 eprintln('${message_signature_error} While deleting the cache file: ${file_path}. DTM initialization failed : ${err.msg()}')
413 return
414 }
415 }
416 }
417 // }
418}
419
420// check_tmpl_and_placeholders_size is used exclusively in the 'expand' function, this check verifies if the template file exists.
421// It also ensures the file extension is correct ( like HTML or TXT ) and controls the size of placeholder keys and values in the provided map,
422// offering a basic validation and security measure against excessively long and potentially harmful inputs.
423// Size limits are defined by the 'max_placeholders_key_size' and 'max_placeholders_value_size' constants.
424// Monitor dynamic content for updates by generating a checksum that is compared against the cached version to verify any changes.
425fn (mut tm DynamicTemplateManager) check_tmpl_and_placeholders_size(f_path string, tmpl_var &map[string]DtmMultiTypeMap) !(string, string, string, TemplateType) {
426 mut html_file := ''
427 mut file_name := ''
428 mut res_checksum_content := ''
429 mut need_to_create_entry := false
430 mut define_file_type := TemplateType.html
431
432 rlock tm.html_file_info {
433 if mapped_html_info := tm.html_file_info[f_path] {
434 html_file = mapped_html_info.file_full_path.clone()
435 file_name = mapped_html_info.file_name.clone()
436 define_file_type = mapped_html_info.file_type
437 } else {
438 need_to_create_entry = true
439 }
440 }
441 if need_to_create_entry {
442 $if test {
443 html_file = f_path
444 } $else {
445 html_file = os.join_path(tm.template_folder, f_path)
446 }
447 if os.exists(html_file) {
448 // Extracts the base file name with extension from the given file path.
449 file_name_with_ext := os.base(html_file)
450 // Removes the file extension, keeping only the name.
451 file_name = file_name_with_ext.all_before_last('.')
452 // Performs a basic check of the file extension.
453 ext := os.file_ext(html_file)
454 if ext != '.html' && ext != '.txt' {
455 eprintln('${message_signature_error} ${html_file}, is not a valid template file like .html or .txt')
456 return error(internat_server_error)
457 }
458 if ext == '.txt' {
459 define_file_type = TemplateType.text
460 }
461 lock tm.html_file_info {
462 tm.html_file_info[f_path.clone()] = HtmlFileInfo{
463 file_full_path: html_file.clone()
464 file_name: file_name.clone()
465 file_type: define_file_type
466 }
467 }
468 } else {
469 eprintln("${message_signature_error} Template : '${html_file}' not found. Ensure all templates are located in the template directory.")
470 return error(internat_server_error)
471 }
472 }
473
474 // checks if the dynamic content ( If, however, it contains dynamic content ) of an template has been updated.
475 // If it has, it creates a checksum of this content for analysis.
476 // The checksum is generated by concatenating all dynamic values and applying a fnv1a hash to the resulting string and generate a checksum.
477 if tmpl_var.len > 0 {
478 mut placeholder_values := []PlaceholderChecksumValue{cap: tmpl_var.len}
479 for key, value in tmpl_var {
480 if key.len > max_placeholders_key_size {
481 eprintln('${message_signature_error} Length of placeholder key "${key}" exceeds the maximum allowed size for template content in file: ${html_file}. Max allowed size: ${max_placeholders_key_size} characters.')
482 return error(internat_server_error)
483 }
484 mut casted_value := ''
485 match value {
486 string {
487 casted_value = value
488 }
489 else {
490 casted_value = value.str()
491 }
492 }
493
494 if casted_value.len > max_placeholders_value_size {
495 eprintln('${message_signature_error} Length of placeholder value for key "${key}" exceeds the maximum allowed size for template content in file: ${html_file}. Max allowed size: ${max_placeholders_value_size} characters.')
496 return error(internat_server_error)
497 }
498 placeholder_values << PlaceholderChecksumValue{
499 key: key.clone()
500 value: casted_value.clone()
501 }
502 }
503 placeholder_values.sort(a.key < b.key)
504 mut combined_str := strings.new_builder(placeholder_values.len * 16)
505 for placeholder_value in placeholder_values {
506 combined_str.write_string(placeholder_value.value)
507 }
508
509 res_checksum_content = fnv1a.sum64_string(combined_str.str()).str()
510 }
511
512 // If all is ok, return full path of template file and filename without extension
513 return html_file, file_name, res_checksum_content, define_file_type
514}
515
516// create_template_cache_and_display is exclusively invoked from `expand`.
517// It generates the template rendering and prepares rendered-cache information
518// for either the creation or updating of the template cache.
519// It starts by ensuring that the cache delay expiration is correctly set by user.
520// It then parses the template file, replacing placeholders with actual dynamics/statics values.
521// If caching is enabled (indicated by a cache delay expiration different from -1) and the template content is valid,
522// the function constructs a `TemplateCache` request with all the necessary details.
523// This request is processed synchronously, so the rendered cache is ready when the function returns.
524fn (mut tm DynamicTemplateManager) create_template_cache_and_display(tcs CacheRequest, last_template_mod i64,
525 unique_time i64, file_path string, tmpl_name string, cache_delay_expiration i64, placeholders &map[string]DtmMultiTypeMap,
526 current_content_checksum string, tmpl_type TemplateType) string {
527 // Control if cache delay expiration is correctly set. See the function itself for more details.
528 check_if_cache_delay_iscorrect(cache_delay_expiration, tmpl_name) or {
529 eprintln(err)
530 return internat_server_error
531 }
532 // Parses the template and stores the rendered output in the variable. See the function itself for more details.
533 mut html := tm.parse_tmpl_file(file_path, tmpl_name, placeholders, tm.compress_html, tmpl_type)
534 // If caching is enabled and the template content is valid, this section creates or updates
535 // the rendered cache synchronously. Keeping the rendered string in the current call avoids
536 // fragile tmp-file ownership/conversion paths in prod builds.
537 if cache_delay_expiration != -1 && html != internat_server_error && tm.active_cache_server {
538 cache_id := tm.id_counter
539 tm.id_counter++
540 old_cache_id := tm.id_to_handlered
541 cache_request := TemplateCache{
542 id: cache_id
543 name: tmpl_name.clone()
544 // 'path' field contains the full path, name and file extension of targeted HTML template.
545 path: file_path.clone()
546 content_checksum: current_content_checksum.clone()
547 // Last modified timestamp of HTML template
548 last_template_mod: last_template_mod
549 // Unix current local timestamp of cache generation request converted to UTC
550 generate_at: unique_time
551 // Defines the cache expiration delay in seconds. This value is added to 'generate_at' to calculate the expiration time of the cache.
552 cache_delay_expiration: cache_delay_expiration
553 html_data: html.clone()
554 // The requested routing to define creation or updating cache.
555 cache_request: tcs
556 old_id: old_cache_id
557 }
558 mut process_request := cache_request
559 tm.process_cache_request(mut process_request)
560 tm.signal_cache_ready()
561 // In the context of a cache update, this function is used to signal that the process has finished using the cache information. The 'nbr_of_remaining_request' counter is therefore updated."
562 if tcs == .update || tcs == .exp_update {
563 tm.remaining_template_request(false, old_cache_id)
564 }
565 } else {
566 // In the context of a template validity error, the 'nbr_of_remaining_request' counter is consistently updated to avoid anomalies in cache management.
567 tm.remaining_template_request(false, tm.id_to_handlered)
568 }
569
570 return html
571}
572
573// get_cache is exclusively invoked from `expand', retrieves the rendered HTML from the cache.
574fn (mut tm DynamicTemplateManager) get_cache(_name string, path string, _placeholders &map[string]DtmMultiTypeMap) string {
575 mut cache_id := 0
576 rlock tm.template_caches {
577 for value in tm.template_caches {
578 // If the cache for the specified HTML template is found, perform the following operations:
579 if value.path == path {
580 cache_id = value.id
581 break
582 }
583 }
584 }
585 if cache_id != 0 {
586 return tm.get_cache_by_id(cache_id)
587 }
588 return ''
589}
590
591fn (mut tm DynamicTemplateManager) get_cache_by_id(cache_id int) string {
592 mut cache_exists := false
593 mut html_data := ''
594 mut cache_storage_mode := CacheStorageMode.memory
595 mut disk_cache_path := ''
596 mut cache_name := ''
597 rlock tm.template_caches {
598 for value in tm.template_caches {
599 if value.id == cache_id {
600 cache_exists = true
601 html_data = value.html_data.clone()
602 cache_storage_mode = value.cache_storage_mode
603 cache_name = value.name.clone()
604 disk_cache_path = if value.cache_full_path_name != '' {
605 value.cache_full_path_name.clone()
606 } else {
607 tm.cache_disk_path(value.name, value.checksum)
608 }
609 break
610 }
611 }
612 }
613 defer {
614 if cache_exists {
615 tm.remaining_template_request(false, cache_id)
616 }
617 }
618 if html_data.len > 0 {
619 return html_data
620 }
621 match cache_storage_mode {
622 .memory {
623 return ''
624 }
625 .disk {
626 cached_html := os.read_file(disk_cache_path) or {
627 eprintln('${message_signature_error} Get_cache() cannot read template cache file ${cache_name} : ${err.msg()} ')
628 return internat_server_error
629 }
630 return cached_html
631 }
632 }
633
634 return ''
635}
636
637// return_cache_info_isexistent is exclusively used in 'expand' to determine whether a cache exists for the provided HTML template.
638// If a cache exists, it returns the necessary information for its transformation. If not, it indicates the need to create a new cache.
639fn (mut tm DynamicTemplateManager) return_cache_info_isexistent(tmpl_path string) (bool, int, string, i64, i64, i64, string) {
640 cache_entries := tm.snapshot_template_caches()
641 mut cache_found := false
642 mut cache_id := 0
643 mut cache_path := ''
644 mut last_template_mod := i64(0)
645 mut generate_at := i64(0)
646 mut cache_delay_expiration := i64(0)
647 mut content_checksum := ''
648 path_hash := fnv1a.sum64_string(tmpl_path)
649 active_cache_id := tm.template_cache_ids_by_path_hash[path_hash] or { 0 }
650 for value in cache_entries {
651 if active_cache_id != 0 {
652 if value.id != active_cache_id {
653 continue
654 }
655 } else if value.path != tmpl_path {
656 continue
657 }
658 // This code section handles cache redirection.
659 // If a cache redirection ID is found, it indicates that the currently used cache is outdated and there's a newer version available.
660 // The process then seeks to retrieve information from this more recent cache.
661 // This is done recursively: if the updated cache itself points to an even newer version, the process continues until the most up-to-date cache is found.
662 // This recursive mechanism ensures that the latest cache data is always used.
663 cache_found = true
664 cache_id = value.id
665 cache_path = value.path.clone()
666 last_template_mod = value.last_template_mod
667 generate_at = value.generate_at
668 cache_delay_expiration = value.cache_delay_expiration
669 content_checksum = value.content_checksum.clone()
670 if value.id_redirection != 0 {
671 mut id_value_recursion := value.id_redirection
672 for {
673 mut found_redirect := false
674 for val in cache_entries {
675 if val.id == id_value_recursion {
676 cache_id = val.id
677 cache_path = val.path.clone()
678 last_template_mod = val.last_template_mod
679 generate_at = val.generate_at
680 cache_delay_expiration = val.cache_delay_expiration
681 content_checksum = val.content_checksum.clone()
682 found_redirect = true
683 break
684 }
685 }
686 if !found_redirect {
687 break
688 }
689 mut next_redirection := 0
690 for val in cache_entries {
691 if val.id == cache_id {
692 next_redirection = val.id_redirection
693 break
694 }
695 }
696 if next_redirection == 0 {
697 break
698 }
699 id_value_recursion = next_redirection
700 }
701 }
702 break
703 }
704 if cache_found {
705 // Function is used to signal that the process has begun using the cache information.
706 tm.remaining_template_request(true, cache_id)
707 return true, cache_id, cache_path, last_template_mod, generate_at, cache_delay_expiration, content_checksum
708 }
709 // No existing cache, need to create it.
710 return false, 0, '', 0, 0, 0, ''
711}
712
713// remaining_template_request manages the counter in 'nbr_of_remaining_template_request',
714// which tracks the number of requests that have started or finished for a specific
715// rendered-cache entry. If a replaced cache is no longer in use, it is deleted.
716fn (mut tm DynamicTemplateManager) remaining_template_request(b bool, v int) {
717 mut delete_request_id := 0
718 lock tm.nbr_of_remaining_template_request {
719 for key, r_request in tm.nbr_of_remaining_template_request {
720 if r_request.id == v {
721 if b == true {
722 // if true, indicating a new request for the cache, Increments the count of active requests.
723 tm.nbr_of_remaining_template_request[key].nbr_of_remaining_request += 1
724 } else {
725 // if false, Decrements the count of active cache requests.
726 tm.nbr_of_remaining_template_request[key].nbr_of_remaining_request -= 1
727 // Checks if the number of active requests is zero or less and if there's a pending delete request for the cache.
728 if tm.nbr_of_remaining_template_request[key].nbr_of_remaining_request <= 0
729 && tm.nbr_of_remaining_template_request[key].need_to_send_delete_request {
730 delete_request_id = r_request.id
731 }
732 }
733
734 break
735 }
736 }
737 }
738 if delete_request_id != 0 {
739 mut delete_request := TemplateCache{
740 id: delete_request_id
741 cache_request: .delete
742 }
743 tm.process_cache_request(mut delete_request)
744 }
745}
746
747// process_cache_request stores or deletes one rendered-cache entry synchronously.
748// Small rendered outputs are kept in memory; larger outputs are written to disk.
749fn (mut tm DynamicTemplateManager) process_cache_request(mut tc TemplateCache) bool {
750 tc.ensure_owned_data()
751
752 // Determine if the request is a duplicate. If so, the cache creation/update request is ignored.
753 is_duplicate_request := tm.chandler_prevent_cache_duplicate_request(tc)
754 if is_duplicate_request {
755 return true
756 }
757
758 if tc.cache_request != .delete {
759 rendered_html := tc.html_data.clone()
760 if tc.html_data.len <= (tm.max_size_data_in_memory * 1024) {
761 tc.cache_storage_mode = .memory
762 } else {
763 tc.cache_storage_mode = .disk
764 }
765
766 combined_str := rendered_html + tc.path + tc.generate_at.str()
767 tc.checksum = md5.hexhash(combined_str).clone()
768
769 match tc.cache_storage_mode {
770 .memory {
771 tc.html_data = rendered_html.clone()
772 }
773 .disk {
774 cache_file_path := tm.cache_disk_path(tc.name, tc.checksum)
775 os.write_file(cache_file_path, rendered_html) or {
776 eprintln('${message_signature_error} Rendered cache: failed to write cache file: ${err.msg()}')
777 return false
778 }
779 tc.cache_full_path_name = cache_file_path.clone()
780 // Keep a hot in-memory copy even for disk-backed entries. The disk file
781 // preserves compatibility, while cache hits avoid fragile string metadata
782 // roundtrips in optimized prod builds.
783 tc.html_data = rendered_html.clone()
784 }
785 }
786 }
787
788 if tc.cache_request != .delete {
789 stable_cache_entry := tc.clone_cache_entry()
790 lock tm.template_caches {
791 tm.template_caches << stable_cache_entry
792 }
793 path_hash := fnv1a.sum64_string(stable_cache_entry.path)
794 tm.template_cache_ids_by_path_hash[path_hash] = stable_cache_entry.id
795 }
796 if tc.cache_request == .new {
797 tm.chandler_remaining_cache_template_used(tc.cache_request, tc.id, tc.old_id)
798 } else {
799 test_b := tm.chandler_remaining_cache_template_used(tc.cache_request, tc.id, tc.old_id)
800 if test_b {
801 clear_cache_id := if tc.cache_request == .delete { tc.id } else { tc.old_id }
802 key, is_success := tm.chandler_clear_specific_cache(clear_cache_id)
803 if !is_success {
804 return false
805 }
806 lock tm.template_caches {
807 tm.template_caches.delete(key)
808 }
809 }
810 }
811 return true
812}
813
814fn (tm &DynamicTemplateManager) cache_disk_path(name string, checksum string) string {
815 cache_file_name := '${name}_${checksum}.cache'
816 return os.join_path(tm.template_cache_folder, cache_file_name).clone()
817}
818
819fn (mut tc TemplateCache) ensure_owned_strings() {
820 tc.name = tc.name.clone()
821 tc.path = tc.path.clone()
822 tc.checksum = tc.checksum.clone()
823 tc.content_checksum = tc.content_checksum.clone()
824 tc.cache_full_path_name = tc.cache_full_path_name.clone()
825}
826
827fn (mut tc TemplateCache) ensure_owned_data() {
828 tc.ensure_owned_strings()
829 tc.html_data = tc.html_data.clone()
830}
831
832fn (tc TemplateCache) clone_cache_entry() TemplateCache {
833 return TemplateCache{
834 id: tc.id
835 name: tc.name.clone()
836 old_id: tc.old_id
837 path: tc.path.clone()
838 checksum: tc.checksum.clone()
839 content_checksum: tc.content_checksum.clone()
840 last_template_mod: tc.last_template_mod
841 generate_at: tc.generate_at
842 cache_delay_expiration: tc.cache_delay_expiration
843 html_data: tc.html_data.clone()
844 cache_request: tc.cache_request
845 cache_storage_mode: tc.cache_storage_mode
846 id_redirection: tc.id_redirection
847 cache_full_path_name: tc.cache_full_path_name.clone()
848 }
849}
850
851fn (tm &DynamicTemplateManager) snapshot_template_caches() []TemplateCache {
852 rlock tm.template_caches {
853 mut entries := []TemplateCache{cap: tm.template_caches.len}
854 for entry in tm.template_caches {
855 entries << entry.clone_cache_entry()
856 }
857 return entries
858 }
859}
860
861fn (mut tm DynamicTemplateManager) signal_cache_ready() {
862 $if test {
863 select {
864 tm.is_ready <- true {}
865 else {}
866 }
867 }
868}
869
870fn (mut tm DynamicTemplateManager) cache_handler() {
871 defer {
872 tm.ch_cache_handler.close()
873 tm.is_ready.close()
874 }
875 for {
876 select {
877 tc := <-tm.ch_cache_handler {
878 if tm.close_cache_handler {
879 eprintln('${message_signature_info} Cache manager has been successfully stopped. Please consider restarting the application if needed.')
880 return
881 }
882 mut request := tc
883 if request.cache_request != .delete && request.old_id == 0 {
884 request.old_id = tm.id_to_handlered
885 }
886 tm.process_cache_request(mut request)
887 tm.signal_cache_ready()
888 }
889 }
890 }
891}
892
893// fn (DynamicTemplateManager) chandler_prevent_cache_duplicate_request(&TemplateCache) return bool
894//
895// Used by rendered-cache processing to assess whether a cache request is a duplicate,
896// based on the type of cache request (.new, .update, .exp_update, .delete) and the existing data.
897// Returns true to indicate a duplicate request, which will be ignored, and false otherwise.
898//
899fn (tm &DynamicTemplateManager) chandler_prevent_cache_duplicate_request(tc &TemplateCache) bool {
900 match tc.cache_request {
901 .new {
902 for value in tm.template_caches {
903 // Evaluate full path
904 if value.path == tc.path {
905 return true
906 }
907 }
908 }
909 .update {
910 for value in tm.template_caches {
911 // Evaluate full path + The timestamp of the last html file modification + checksum of content.
912 if value.path == tc.path && value.last_template_mod == tc.last_template_mod
913 && value.content_checksum == tc.content_checksum {
914 return true
915 }
916 }
917 }
918 .exp_update {
919 for value in tm.template_caches {
920 // Evaluate full path + The timestamp of the last html modification file +
921 // Checks if the current cache generation time is within the expiration window set from the last update.
922 if value.path == tc.path && value.last_template_mod == tc.last_template_mod {
923 if tc.generate_at < (value.generate_at + value.cache_delay_expiration) {
924 return true
925 }
926 }
927 }
928 }
929 .delete {
930 for value in tm.template_caches {
931 // Evaluate ID to test if cache is always in database.
932 if value.id == tc.id {
933 return false
934 }
935 }
936 // Otherwise cache has already been deleted
937 return true
938 }
939 else {}
940 }
941
942 return false
943}
944
945// fn (mut DynamicTemplateManager) chandler_clear_specific_cache(int) return (int, bool)
946//
947// Used to remove specific rendered-cache information from the database when necessary.
948// It identifies the target 'TemplateCache' by its id in the array database and deletes its corresponding cache file in 'disk mode'.
949//
950fn (mut tm DynamicTemplateManager) chandler_clear_specific_cache(id int) (int, bool) {
951 for key, value in tm.template_caches {
952 if value.id == id {
953 path_hash := fnv1a.sum64_string(value.path)
954 if active_cache_id := tm.template_cache_ids_by_path_hash[path_hash] {
955 if active_cache_id == id {
956 tm.template_cache_ids_by_path_hash.delete(path_hash)
957 }
958 }
959 match value.cache_storage_mode {
960 .memory {}
961 .disk {
962 file_path := tm.cache_disk_path(value.name, value.checksum)
963 os.rm(file_path) or {
964 eprintln('${message_signature_error} While deleting the specific cache file: ${file_path}: ${err.msg()}')
965 break
966 }
967 }
968 }
969
970 return key, true
971 }
972 }
973 return 0, false
974}
975
976// fn (mut DynamicTemplateManager) chandler_remaining_cache_template_used(CacheRequest, int, int) return bool
977//
978// Manages the lifecycle of rendered-cache requests in 'nbr_of_remaining_template_request'.
979// For each cache request (creation, update, or deletion), it updates the status of ongoing requests and decides on necessary actions.
980// The function returns a boolean value: if true, it authorizes the destruction of the expired cache, ensuring that it's only removed when no longer in use.
981// If false, it indicates that the cache cannot yet be destroyed due to ongoing usage.
982//
983fn (mut tm DynamicTemplateManager) chandler_remaining_cache_template_used(cr CacheRequest, id int, old_id int) bool {
984 lock tm.nbr_of_remaining_template_request {
985 match cr {
986 // Adds a new request in 'nbr_of_remaining_template_request' to track the usage of the newly created cache
987 .new {
988 tm.nbr_of_remaining_template_request << RemainingTemplateRequest{
989 id: id
990 }
991 }
992 .update, .exp_update {
993 // Marks the old cache as obsolete and adds a request for the new cache.
994 // If the old cache is no longer in use, it is immediately deleted. Otherwise, a flag is set for deferred deletion of the cache as soon as feasible
995 for key := 0; key < tm.nbr_of_remaining_template_request.len; key++ {
996 if tm.nbr_of_remaining_template_request[key].id == old_id {
997 tm.nbr_of_remaining_template_request[key].need_to_delete = true
998 // If possible, immediately deleted request
999 if tm.nbr_of_remaining_template_request[key].nbr_of_remaining_request <= 0
1000 && tm.nbr_of_remaining_template_request[key].need_to_delete == true {
1001 tm.nbr_of_remaining_template_request.delete(key)
1002 tm.nbr_of_remaining_template_request << RemainingTemplateRequest{
1003 id: id
1004 }
1005 return true
1006 // else, set for deferred deletion of the cache as soon as feasible
1007 } else {
1008 tm.nbr_of_remaining_template_request[key].need_to_send_delete_request = true
1009 tm.nbr_of_remaining_template_request << RemainingTemplateRequest{
1010 id: id
1011 }
1012 }
1013 break
1014 }
1015 }
1016 return false
1017 }
1018 .delete {
1019 // Removes the cache request from the list if it's no longer in use.
1020 for key := 0; key < tm.nbr_of_remaining_template_request.len; key++ {
1021 if tm.nbr_of_remaining_template_request[key].id == id {
1022 if tm.nbr_of_remaining_template_request[key].nbr_of_remaining_request <= 0
1023 && tm.nbr_of_remaining_template_request[key].need_to_delete == true {
1024 tm.nbr_of_remaining_template_request.delete(key)
1025 }
1026 break
1027 }
1028 }
1029 }
1030 else {}
1031 }
1032 }
1033
1034 return true
1035}
1036
1037// fn (mut DynamicTemplateManager) parse_tmpl_file(string, string, &map[string]DtmMultiTypeMap, bool, TemplateType) return (string, string)
1038//
1039// Parses and generates template file content.
1040// It ensures template format compatibility necessary for proper compilation and execution in its typical usage outside of DTM like managing various states,
1041// processing template tags, and supporting string interpolation...
1042// including dynamic content with the possibility of adding HTML code but only for certain specified tags and can also light compress HTML if required ( Removing usless spaces ).
1043//
1044const allowed_tags = ['<div>', '</div>', '<h1>', '</h1>', '<h2>', '</h2>', '<h3>', '</h3>', '<h4>',
1045 '</h4>', '<h5>', '</h5>', '<h6>', '</h6>', '<p>', '</p>', '<br>', '<hr>', '<span>', '</span>',
1046 '<ul>', '</ul>', '<ol>', '</ol>', '<li>', '</li>', '<dl>', '</dl>', '<dt>', '</dt>', '<dd>',
1047 '</dd>', '<menu>', '</menu>', '<table>', '</table>', '<caption>', '</caption>', '<th>', '</th>',
1048 '<tr>', '</tr>', '<td>', '</td>', '<thread>', '</thread>', '<tbody>', '</tbody>', '<tfoot>',
1049 '</tfoot>', '<col>', '</col>', '<colgroup>', '</colgroup>', '<header>', '</header>', '<footer>',
1050 '</footer>', '<main>', '</main>', '<section>', '</section>', '<article>', '</article>', '<aside>',
1051 '</aside>', '<details>', '</details>', '<dialog>', '</dialog>', '<data>', '</data>', '<summary>',
1052 '</summary>']
1053
1054const include_html_key_tag = '_#includehtml'
1055
1056fn (mut tm DynamicTemplateManager) parse_tmpl_file(file_path string, tmpl_name string, placeholders &map[string]DtmMultiTypeMap,
1057 is_compressed bool, tmpl_type TemplateType) string {
1058 mut tmpl_ := compile_template_file(file_path, tmpl_name, placeholders)
1059
1060 // Performs a light compression of the HTML output by removing usless spaces, newlines, and tabs if user selected this option.
1061 if is_compressed && tmpl_type == TemplateType.html && tmpl_ != internat_server_error {
1062 tmpl_ = compress_html_output(tmpl_)
1063 }
1064
1065 return tmpl_
1066}
1067
1068fn compress_html_output(html string) string {
1069 mut compressed := html.replace('\n', '').replace('\t', '')
1070 for compressed.contains(' ') {
1071 compressed = compressed.replace(' ', ' ')
1072 }
1073 mut result := strings.new_builder(compressed.len)
1074 mut i := 0
1075 for i < compressed.len {
1076 result.write_u8(compressed[i])
1077 if compressed[i] == `>` {
1078 mut j := i + 1
1079 for j < compressed.len && compressed[j] == ` ` {
1080 j++
1081 }
1082 if j < compressed.len && compressed[j] == `<` {
1083 i = j
1084 continue
1085 }
1086 }
1087 i++
1088 }
1089 return result.str()
1090}
1091
1092// fn check_if_cache_delay_iscorrect(i64, string) return !
1093//
1094// Validates the user-specified cache expiration delay for templates.
1095// It enforces three permissible delay settings:
1096// - A minimum of five minutes and a maximum of one year for standard cache expiration. ( Define in constants )
1097// - A parameter of 0 for an infinite cache expiration delay
1098// - A parameter of -1 for no caching, meaning the template is processed every time without being stored in the cache."
1099//
1100fn check_if_cache_delay_iscorrect(cde i64, tmpl_name string) ! {
1101 if (cde != 0 && cde != -1 && cde < converted_cache_delay_expiration_at_min)
1102 || (cde != 0 && cde != -1 && cde > converted_cache_delay_expiration_at_max) {
1103 return error("${message_signature_error} The cache timeout for template '${tmpl_name}' cannot be set to a value less than '${cache_delay_expiration_at_min}' seconds and more than '${cache_delay_expiration_at_max}' seconds. Exception for the value '0' which means no cache expiration, and the value '-1' which means html generation without caching.")
1104 }
1105}
1106
1107// fn cache_request_route(bool, i64, i64, i64, i64, i64, i64) return (CacheRequest, i64)
1108//
1109// Used exclusively in 'expand' function, determines the appropriate cache request action for a template.
1110// It assesses various conditions such as cache existence, cache expiration settings, and last modification timestamps ( template or dynamic content )
1111// to decide whether to create a new cache, update an existing or delivered a valid cache content.
1112//
1113fn (mut tm DynamicTemplateManager) cache_request_route(is_cache_exist bool, neg_cache_delay_expiration i64,
1114 last_template_mod i64, test_current_template_mod i64, cache_del_exp i64, gen_at i64, c_time i64,
1115 content_checksum string, current_content_checksum string) (CacheRequest, i64) {
1116 current_ts := if c_time > 0 { c_time } else { tm.next_cache_timestamp() }
1117 if !is_cache_exist || neg_cache_delay_expiration == -1 {
1118 // Require cache creation
1119 unique_ts := tm.next_cache_timestamp()
1120 return CacheRequest.new, unique_ts
1121 } else if last_template_mod < test_current_template_mod
1122 || content_checksum != current_content_checksum {
1123 // Requires cache update as the template has been modified since the last time. it can be the template itself or its dynamic content.
1124 unique_ts := tm.next_cache_timestamp()
1125 return CacheRequest.update, unique_ts
1126 } else if cache_del_exp != 0 && (gen_at + cache_del_exp) < current_ts {
1127 // Requires cache update as the cache expiration delay has elapsed.
1128 unique_ts := tm.next_cache_timestamp()
1129 return CacheRequest.exp_update, unique_ts
1130 } else {
1131 // Returns valid cached content, no update or creation necessary.
1132 return CacheRequest.cached, 0
1133 }
1134}
1135
1136fn (mut tm DynamicTemplateManager) next_cache_timestamp() i64 {
1137 current_ts := get_current_unix_micro_timestamp()
1138 if current_ts <= tm.c_time {
1139 tm.c_time++
1140 } else {
1141 tm.c_time = current_ts
1142 }
1143 return tm.c_time
1144}
1145
1146// fn get_current_unix_timestamp() return i64
1147//
1148// This function is designed for handling timezone adjustments by converting the machine's local time at micro format to a universal micro format.
1149//
1150fn get_current_unix_micro_timestamp() i64 {
1151 return time.now().unix_micro()
1152}
1153