diff --git a/vlib/vweb/tests/dynamic_template_manager_test_server/dynamic_template_manager_test_server.v b/vlib/vweb/tests/dynamic_template_manager_test_server/dynamic_template_manager_test_server.v
new file mode 100644
index 0000000000..c99e27c8dd
--- /dev/null
+++ b/vlib/vweb/tests/dynamic_template_manager_test_server/dynamic_template_manager_test_server.v
@@ -0,0 +1,65 @@
+import vweb
+import time
+import x.templating.dtm
+import os
+
+struct App {
+ vweb.Context
+pub mut:
+ dtm &dtm.DynamicTemplateManager = dtm.create_dtm() @[vweb_global]
+ shared_data_string []string @[vweb_global]
+ shared_data_int []int @[vweb_global]
+ shared_switch int = 1 @[vweb_global]
+}
+
+fn main() {
+ mut app := &App{}
+
+ app.shared_data_string << 'vweb dtm'
+ app.shared_data_string << 'VWEB DTM'
+ app.shared_data_string << 'this
is
a
test
with
included
HTML!'
+ app.shared_data_string << 'THIS
NOT
A
TEST!'
+ app.shared_data_string << '
escaped html tags test
'
+ app.shared_data_int << 123456
+ app.shared_data_int << 7891011
+
+ dtm.initialize_dtm(mut app.dtm) or { eprintln(err) }
+ /*
+ app.initialize_dtm(mut &app.dtm,
+ compress_html: false
+ active_cache_server: false
+ max_size_data_in_mem: 100) or { eprintln(err) }
+ */
+ go app.update_data()
+
+ app.mount_static_folder_at(os.resource_abs_path('static'), '/')
+
+ vweb.run(app, 18081)
+}
+
+@['/']
+pub fn (mut app App) index() vweb.Result {
+ mut tmpl_var := map[string]dtm.DtmMultiTypeMap{}
+ tmpl_var['title'] = app.shared_data_string[app.shared_switch]
+ tmpl_var['non_string_type'] = app.shared_data_int[app.shared_switch]
+ tmpl_var['string_type'] = app.shared_data_string[4]
+ tmpl_var['html_#includehtml'] = app.shared_data_string[app.shared_switch + 2]
+
+ // You can also modify the HTML template file directly without having to recompile the application.
+ html_content := app.dtm.serve_dynamic_template('index.html',
+ placeholders: &tmpl_var
+ cache_delay_expiration: dtm.cache_delay_expiration_at_min
+ )
+ return app.html(html_content)
+}
+
+fn (mut app App) update_data() {
+ for {
+ if app.shared_switch == 1 {
+ app.shared_switch = 0
+ } else {
+ app.shared_switch = 1
+ }
+ time.sleep(10 * time.second)
+ }
+}
diff --git a/vlib/vweb/tests/dynamic_template_manager_test_server/static/index.css b/vlib/vweb/tests/dynamic_template_manager_test_server/static/index.css
new file mode 100644
index 0000000000..f958fadd29
--- /dev/null
+++ b/vlib/vweb/tests/dynamic_template_manager_test_server/static/index.css
@@ -0,0 +1,30 @@
+body {
+ font-family: Arial, sans-serif;
+ background-color: #f8f8f8;
+ color: #333;
+ margin: 0;
+ padding: 0;
+}
+
+#container {
+ width: 80%;
+ margin: 0 auto;
+ background-color: #fff;
+ padding: 20px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+h1 {
+ color: #007bff;
+ text-align: center;
+}
+
+p {
+ font-size: 16px;
+ line-height: 1.6;
+}
+
+span {
+ color: #28a745;
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/vlib/vweb/tests/dynamic_template_manager_test_server/templates/comment.html b/vlib/vweb/tests/dynamic_template_manager_test_server/templates/comment.html
new file mode 100644
index 0000000000..d09d44e1e8
--- /dev/null
+++ b/vlib/vweb/tests/dynamic_template_manager_test_server/templates/comment.html
@@ -0,0 +1,2 @@
+This is from a file inclusion!
+DTM
\ No newline at end of file
diff --git a/vlib/vweb/tests/dynamic_template_manager_test_server/templates/index.html b/vlib/vweb/tests/dynamic_template_manager_test_server/templates/index.html
new file mode 100644
index 0000000000..a64d51bffd
--- /dev/null
+++ b/vlib/vweb/tests/dynamic_template_manager_test_server/templates/index.html
@@ -0,0 +1,16 @@
+
+
+
+ @title
+ @css '/index.css'
+
+
+ @include 'comment.html'
+
+
@title
+
@non_string_type
+
@string_type
+ @html
+
+
+
diff --git a/vlib/vweb/tests/dynamic_template_manager_test_server/vcache/.gitkeep b/vlib/vweb/tests/dynamic_template_manager_test_server/vcache/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/vlib/x/templating/dtm/dynamic_template_manager.v b/vlib/x/templating/dtm/dynamic_template_manager.v
new file mode 100644
index 0000000000..be04707592
--- /dev/null
+++ b/vlib/x/templating/dtm/dynamic_template_manager.v
@@ -0,0 +1,1225 @@
+module dtm
+
+import os
+import v.parser
+import crypto.md5
+import hash.fnv1a
+import time
+import regex
+
+// These are all the types of dynamic values that the DTM allows to be returned in the context of a map
+type DtmMultiTypeMap = f32 | f64 | i16 | i64 | i8 | int | string | u16 | u32 | u64 | u8
+
+// type MiddlewareFn = fn (mut Context, string) bool
+
+// cache_delay_expiration_at_min is the minimum setting for cache expiration delay, fixed at 5 minutes (measured in seconds).
+pub const cache_delay_expiration_at_min = 300
+// cache_delay_expiration_at_max maximal is the maximal setting for cache expiration delay, fixed at 1 year (measured in seconds).
+pub const cache_delay_expiration_at_max = 31536000
+// cache_delay_expiration_by_default is the default setting for cache expiration delay, fixed at 1 day (measured in seconds).
+pub const cache_delay_expiration_by_default = 86400
+// Setting channel capacity of the cache handler.
+const cache_handler_channel_cap = 200
+// Setting the maximum data size (500 KB) to be stored at memory mode. If this limit is exceeded, cache hander switch to disk mode.
+const max_size_data_in_memory = 500
+// Defines the maximum character length for placeholder keys.
+const max_placeholders_key_size = 50
+// Sets the maximum character length for placeholder values.
+const max_placeholders_value_size = 3000
+// Internal DTM operations utilize microseconds for time management, primarily aims to minimize the risk of collisions during the creation of temporary cache files
+// especially in cases of simultaneous requests that do not yet have a cache.
+const convert_seconds = i64(1000000)
+const converted_cache_delay_expiration_at_min = i64(cache_delay_expiration_at_min) * convert_seconds
+const converted_cache_delay_expiration_at_max = i64(cache_delay_expiration_at_max) * convert_seconds
+
+const internat_server_error = 'Internal Server Error'
+
+const message_signature = '[Dynamic Template Manager]'
+const message_signature_info = '[Dynamic Template Manager] Info :'
+const message_signature_error = '[Dynamic Template Manager] Error :'
+const message_signature_warn = '[Dynamic Template Manager] Warning :'
+
+// CacheStorageMode
+pub enum CacheStorageMode {
+ memory
+ disk
+}
+
+enum CacheRequest {
+ new
+ update
+ exp_update
+ cached
+ delete
+}
+
+// DynamicTemplateManager
+@[heap]
+pub struct DynamicTemplateManager {
+mut:
+ // Determines if the DTM initialization was performed successfully and if DTM is usable
+ dtm_init_is_ok bool
+ // Store the path to the cache directory.
+ template_cache_folder string
+ // Store the path to the HTML templates directory.
+ template_folder string
+ // cache database
+ template_caches shared []TemplateCache = []TemplateCache{}
+ // counter for each individual TemplateCache created/updated
+ id_counter int = 1
+ ch_cache_handler chan TemplateCache = chan TemplateCache{cap: dtm.cache_handler_channel_cap}
+ // 'id_to_handlered' field is used exclusively by the cache handler to update or delete specific 'TemplateCache' in the cache database.
+ id_to_handlered int
+ close_cache_handler bool
+ // Initialisation params options for these two (Compress_html and active_cache_server)
+ compress_html bool = true
+ active_cache_server bool = true
+ // Initialisation of max data size in memory storage
+ max_size_data_in_memory int = dtm.max_size_data_in_memory
+ // This array is designed to store a control process that checks whether cached data is currently in use while simultaneously handling expiration.
+ // This allows for the harmonious management of both aspects and facilitates the necessary actions.
+ nbr_of_remaining_template_request shared []RemainingTemplateRequest = []RemainingTemplateRequest{}
+ // Dtm clock
+ c_time i64
+ ch_stop_dtm_clock chan bool
+ // Store small informations about already cached pages to improve the verification speed of the check_html_and_placeholders_size function.
+ html_file_info shared map[string]HtmlFileInfo = map[string]HtmlFileInfo{}
+}
+
+// Represent individual template cache in database memory.
+@[noinit]
+struct TemplateCache {
+mut:
+ id int
+ name string
+ // 'path' field contains the full path, name and file extension of targeted HTML template.
+ path string
+ // 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.
+ // This approach is utilized to create a unique identifier."
+ checksum string
+ // The checksum for the dynamic content of an HTML Template is constructed as follows: all dynamic values are concatenated into a single string,
+ // and the checksum is then calculated based on this string.
+ // 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.
+ content_checksum string
+ // Name of the temporary template created to hold the content until the cache manager processes and handles the request accordingly.
+ // This file is then deleted once it has been utilized.
+ tmp_name_file string
+ // The timestamp of the last modification of the file (HTML template)
+ last_template_mod i64
+ // Timestamp of cache generation.
+ generate_at i64
+ // Timestamp of cache expiration define by user.
+ cache_delay_expiration i64
+ html_data []u8
+ cache_request CacheRequest
+ cache_storage_mode CacheStorageMode
+ // 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.
+ // This enables the eventual deletion of the obsolete cache without causing issues for requests utilizing the cache.
+ id_redirection int
+ // Contains the full path to a cache stored on disk.
+ cache_full_path_name string
+}
+
+// Represents controls stored in the 'nbr_of_remaining_template_request' of the DTM.
+// It is used to monitor whether a HTML cache template is in use in 'template_caches' and to manage cache deletion procedures
+// as soon as they become feasible and if required of course.
+@[noinit]
+struct RemainingTemplateRequest {
+ // The id is similar to the one it represents in template_caches.
+ id int
+mut:
+ // This value determines the number of ongoing requests using the cache specified by the ID.
+ nbr_of_remaining_request int
+ // The cache has become obsolete, and permission to dispose of it is granted to the cache manager
+ need_to_delete bool
+ // If the cache manager is unable to destroy an obsolete cache (due to it still being actively used),
+ // then this boolean allows for the retransmission of the request, enabling action to be taken in the near future.
+ need_to_send_delete_request bool
+}
+
+@[noinit]
+struct HtmlFileInfo {
+ file_full_path string
+ file_name string
+}
+
+// TemplateCacheParams are used to specify cache expiration delay and provide placeholder data for substitution in templates.
+@[params]
+pub struct TemplateCacheParams {
+ placeholders &map[string]DtmMultiTypeMap = &map[string]DtmMultiTypeMap{}
+ cache_delay_expiration i64 = dtm.cache_delay_expiration_by_default
+}
+
+// DynamicTemplateManagerInitialisationParams is used with 'initialize_dtm' function. (See below at initialize_dtm section)
+@[params]
+pub struct DynamicTemplateManagerInitialisationParams {
+ compress_html bool = true
+ active_cache_server bool = true
+ max_size_data_in_mem int = dtm.max_size_data_in_memory
+ test_cache_dir string
+ test_template_dir string
+}
+
+// create_dtm initializes and returns a reference to a new instance of DynamicTemplateManager on the heap.
+pub fn create_dtm() &DynamicTemplateManager {
+ return &DynamicTemplateManager{}
+}
+
+// initialize_dtm init the 'DynamicTemplateManager' with the storage mode, cache/templates path folders. The DTM requires this initialization function to be operational.
+// A "vcache" directory must be created at the root of the project (where the executable is located) to use the DTM.
+// Initalisation params are :
+// - 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)
+// - compress_html: 'type bool' Light compress of the HTML ouput. ( default is true )
+// - active_cache_server: 'type bool' Activate or not the template cache system. ( default is true )
+// - test_cache_dir: 'type string' Used only for DTM internal test file, parameter is ignored otherwise.
+// - test_template_dir: 'type string' Used only for DTM internal test file, parameter is ignored otherwise.
+//
+// vfmt off
+pub fn initialize_dtm(mut dtmref &DynamicTemplateManager, dtm_init_params DynamicTemplateManagerInitialisationParams) ! {
+ // vfmt on
+ mut dir_path := ''
+ mut dir_html_path := ''
+ $if test {
+ dir_path = dtm_init_params.test_cache_dir
+ dir_html_path = dtm_init_params.test_template_dir
+ } $else {
+ dir_path = os.join_path('${os.dir(os.executable())}/vcache')
+ dir_html_path = os.join_path('${os.dir(os.executable())}/templates')
+ }
+ // Control if 'vcache' folder exist in the root project
+ if os.exists(dir_path) && os.is_dir(dir_path) {
+ dtmref.template_cache_folder = dir_path
+ // WARNING: When setting the directory for caching files and for testing purposes,
+ // 'check_and_clear_cache_files' function will delete all "*.cache" or "*.tmp" files inside the specified 'vcache' directory in the project root's. Ensure that
+ // directory used for the cache does not contain any important files.
+ dtmref.check_and_clear_cache_files()!
+ // Control if 'templates' folder exist in the root project
+ if !os.exists(dir_html_path) && !os.is_dir(dir_html_path) {
+ return error('${dtm.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}"')
+ } else {
+ dtmref.template_folder = dir_html_path
+ }
+ } else {
+ return error('${dtm.message_signature_error} The cache storage directory at the project root does not exist. Please create a "vcache" directory at the root of your project with appropriate read/write permissions. This is a mandatory step for using the Dynamic Template Manager (DTM). Current path attempted for create the cache folder: "${dir_path}"')
+ }
+ // 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.
+ if dtm_init_params.max_size_data_in_mem <= dtm.max_size_data_in_memory
+ && dtm_init_params.max_size_data_in_mem >= 0 {
+ dtmref.max_size_data_in_memory = dtm_init_params.max_size_data_in_mem
+ } else {
+ mut type_error := 'exceeds'
+ if dtm_init_params.max_size_data_in_mem < 0 {
+ type_error = 'is invalid for define'
+ }
+ eprintln('${dtm.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 ${dtm.max_size_data_in_memory}KB.')
+ }
+
+ // Disable light HTML compression if user doesn't required. ( By default is ON )
+ if !dtm_init_params.compress_html {
+ dtmref.compress_html = false
+ }
+ // Disable cache handler if user doesn't required. Else, new thread is used to start the cache system. ( By default is ON )
+ if !dtm_init_params.active_cache_server {
+ dtmref.active_cache_server = false
+ } else {
+ spawn dtmref.cache_handler()
+ spawn dtmref.handle_dtm_clock()
+ }
+ dtmref.c_time = get_current_unix_micro_timestamp()
+ dtmref.dtm_init_is_ok = true
+ println('${dtm.message_signature} Dynamic Template Manager mode activated')
+}
+
+/*
+fn init_cache_block_middleware(cache_dir string, mut dtm &DynamicTemplateManager) {
+ // Fonction locale correspondant à la signature MiddlewareFn
+ dtm.cache_block_middleware = fn (mut ctx Context, cache_dir string) bool {
+ request_path := ctx.req.url
+ if request_path.contains(cache_dir) {
+ ctx.text('Access to the cache directory is not allowed.')
+ return false
+ }
+ return true
+ }
+}
+*/
+
+// serve_dynamic_template manages the cache and returns generated HTML.
+// Requires an initialization via 'initialize_dtm' to running.
+// To use this function, HTML templates must be located in the 'templates' directory at the project's root.
+// However, it allows the use of subfolder paths within the 'templates' directory,
+// enabling users to structure their templates in a way that best suits their project's organization.
+pub fn (mut tm DynamicTemplateManager) serve_dynamic_template(tmpl_path string, tmpl_var TemplateCacheParams) string {
+ if tm.dtm_init_is_ok {
+ file_path, tmpl_name, current_content_checksum := tm.check_html_and_placeholders_size(tmpl_path,
+ tmpl_var.placeholders) or { return err.msg() }
+ converted_cache_delay_expiration := i64(tmpl_var.cache_delay_expiration) * dtm.convert_seconds
+ // If cache exist, return necessary fields else, 'is_cache_exist' return false.
+ is_cache_exist, id, path, mut last_template_mod, gen_at, cache_del_exp, content_checksum := tm.return_cache_info_isexistent(file_path)
+ mut html := ''
+ // Definition of several variables used to assess the need for cache updates.sss
+ // This determination is based on modifications within the HTML template itself.
+ // `last_template_mod` is set to 0 and `test_current_template_mod` to `i64(0)` when a new cache needs to be created.
+ // `test_current_template_mod` is utilized for updating the cache.
+ mut test_current_template_mod := i64(0)
+ if last_template_mod == 0 {
+ // Get last modification timestamp of HTML template to adding info for the creation of cache.
+ last_template_mod = os.file_last_mod_unix(file_path)
+ } else {
+ // Get last modification timestamp of HTML template to compare with cache info already existant.
+ test_current_template_mod = os.file_last_mod_unix(file_path)
+ }
+
+ // From this point, all the previously encountered variables are used to determine the routing in rendering the HTML template and creating/using its cache.
+ cash_req, unique_time := tm.cache_request_route(is_cache_exist, converted_cache_delay_expiration,
+ last_template_mod, test_current_template_mod, cache_del_exp, gen_at, tm.c_time,
+ content_checksum, current_content_checksum)
+ // Each of these match statements aims to provide HTML rendering, but each one sends a specific signal 'cash_req' of type CacheRequest
+ // or calls the appropriate function for managing the cache of the provided HTML.
+ match cash_req {
+ .new {
+ // Create a new cache
+ html = tm.create_template_cache_and_display_html(cash_req, last_template_mod,
+ unique_time, file_path, tmpl_name, converted_cache_delay_expiration,
+ tmpl_var.placeholders, current_content_checksum)
+ // println('create cache : ${cash_req}')
+ }
+ .update, .exp_update {
+ // Update an existing cache
+ tm.id_to_handlered = id
+ html = tm.create_template_cache_and_display_html(cash_req, test_current_template_mod,
+ unique_time, file_path, tmpl_name, converted_cache_delay_expiration,
+ tmpl_var.placeholders, current_content_checksum)
+ // println('update cache : ${cash_req}')
+ }
+ else {
+ // Use the provided cache of html template.
+ html = tm.get_cache(tmpl_name, path, tmpl_var.placeholders)
+ // println('get cache : ${cash_req}')
+ }
+ }
+ return html
+ } else {
+ tm.stop_cache_handler()
+ eprintln('${dtm.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.')
+ return dtm.internat_server_error
+ }
+}
+
+// fn (DynamicTemplateManager) check_and_clear_cache_files()
+//
+// Used exclusively during the initialization of the DTM (Dynamic Template Manager).
+// Its primary purpose is to ensure a clean starting environment by clearing all files
+// within the designated cache directory. It iterates through the directory, listing all files,
+// and systematically removes each file found, ensuring that no residual cache data persists
+// from previous executions. Additionally, this function also tests the read and write permissions
+// of the cache directory to ensure that the application has the necessary access to properly manage the cache files.
+//
+// WARNING: When setting the directory for caching files and for testing purposes,
+// this function will delete all "*.cache" or "*.tmp" files inside the 'vcache' directory in the project root's. Ensure that
+// directory used for the cache does not contain any important files.
+//
+fn (tm DynamicTemplateManager) check_and_clear_cache_files() ! {
+ // println('${message_signature} WARNING! DTM needs to perform some file tests in the 'vcache' directory in your project. This operation will erase all "*.cache" or "*.tmp" files content in the folder : "${tm.template_cache_folder}"')
+ // println('Do you want to continue the operation? (yes/no)')
+ // mut user_response := os.input('>').to_lower()
+ // if user_response != 'yes' && user_response != 'y' {
+ // return error('${message_signature_error} Operation cancelled by the user. DTM initialization failed.')
+ // } else {
+ file_p := os.join_path(tm.template_cache_folder, 'test.tmp')
+ // Create a text file for test permission access
+ mut f := os.create(file_p) or {
+ return error('${dtm.message_signature_error} Files are not writable. Test fail, DTM initialization failed : ${err.msg()}')
+ }
+ f.close()
+ // Read the previous text file for test permission access
+ os.read_file(file_p) or {
+ return error('${dtm.message_signature_error} Files are not readable. Test fail, DTM initialization failed : ${err.msg()}')
+ }
+ // List all files in the cache folder
+ files_list := os.ls(tm.template_cache_folder) or {
+ return error('${dtm.message_signature_error} While listing the cache directorie files, DTM initialization failed : ${err.msg()}')
+ }
+ // Delete one by one "*.cache" or "*.tmp" files in the previous file list
+ for file in files_list {
+ file_path := os.join_path(tm.template_cache_folder, file)
+ file_extension := os.file_ext(file_path).to_lower()
+ if file_extension in ['.tmp', '.cache'] {
+ os.rm(file_path) or {
+ eprintln('${dtm.message_signature_error} While deleting the cache file: ${file_path}. DTM initialization failed : ${err.msg()}')
+ return
+ }
+ }
+ }
+ // }
+}
+
+// fn (mut DynamicTemplateManager) check_html_and_placeholders_size(string, &map[string]DtmMultiTypeMap) return !(string, string, string)
+//
+// Used exclusively in the 'serve_dynamic_template' function, this check verifies if the HTML file exists and is located within the 'templates' directory at the project's root.
+// It also ensures the file extension is HTML and controls the size of placeholder keys and values in the provided map,
+// offering a basic validation and security measure against excessively long and potentially harmful inputs.
+// Size limits are defined by the 'max_placeholders_key_size' and 'max_placeholders_value_size' constants.
+// Monitor dynamic content for updates by generating a checksum that is compared against the cached version to verify any changes.
+//
+fn (mut tm DynamicTemplateManager) check_html_and_placeholders_size(f_path string, tmpl_var &map[string]DtmMultiTypeMap) !(string, string, string) {
+ mut html_file := ''
+ mut file_name := ''
+ mut res_checksum_content := ''
+ mut need_to_create_entry := false
+
+ rlock tm.html_file_info {
+ if mapped_html_info := tm.html_file_info[f_path] {
+ html_file = mapped_html_info.file_full_path
+ file_name = mapped_html_info.file_name
+ } else {
+ need_to_create_entry = true
+ }
+ }
+ if need_to_create_entry {
+ $if test {
+ html_file = f_path
+ } $else {
+ html_file = os.join_path(tm.template_folder, f_path)
+ }
+ if os.exists(html_file) {
+ // Extracts the base file name with extension from the given file path.
+ file_name_with_ext := os.base(html_file)
+ // Removes the file extension, keeping only the name.
+ file_name = file_name_with_ext.all_before_last('.')
+ // Performs a basic check of the file extension.
+ ext := os.file_ext(html_file)
+ if ext != '.html' {
+ eprintln('${dtm.message_signature_error} ${html_file}, is not a HTML file')
+ return error(dtm.internat_server_error)
+ }
+ lock tm.html_file_info {
+ tm.html_file_info[f_path] = HtmlFileInfo{
+ file_full_path: html_file
+ file_name: file_name
+ }
+ }
+ } else {
+ eprintln("${dtm.message_signature_error} Template : '${html_file}' not found. Ensure all HTML templates are located in the 'templates' directory at the project's root.")
+ return error(dtm.internat_server_error)
+ }
+ }
+
+ // checks if the dynamic content ( If, however, it contains dynamic content ) of an HTML page has been updated.
+ // If it has, it creates a checksum of this content for analysis.
+ // The checksum is generated by concatenating all dynamic values and applying a fnv1a hash to the resulting string and generate a checksum.
+ if tmpl_var.len > 0 {
+ mut combined_str := ''
+ // Control placeholder key and value sizes
+ for key, value in tmpl_var {
+ if key.len > dtm.max_placeholders_key_size {
+ eprintln('${dtm.message_signature_error} Length of placeholder key "${key}" exceeds the maximum allowed size for HTML content in file: ${html_file}. Max allowed size: ${dtm.max_placeholders_key_size} characters.')
+ return error(dtm.internat_server_error)
+ }
+ match value {
+ string {
+ if value.len > dtm.max_placeholders_value_size {
+ eprintln('${dtm.message_signature_error} Length of placeholder value for key "${key}" exceeds the maximum allowed size for HTML content in file: ${html_file}. Max allowed size: ${dtm.max_placeholders_value_size} characters.')
+ return error(dtm.internat_server_error)
+ }
+ combined_str += value
+ }
+ else {
+ casted_value := value.str()
+ if casted_value.len > dtm.max_placeholders_value_size {
+ eprintln('${dtm.message_signature_error} Length of placeholder value for key "${key}" exceeds the maximum allowed size for HTML content in file: ${html_file}. Max allowed size: ${dtm.max_placeholders_value_size} characters.')
+ return error(dtm.internat_server_error)
+ }
+
+ combined_str += casted_value
+ }
+ }
+ }
+
+ res_checksum_content = fnv1a.sum64_string(combined_str).str()
+ }
+
+ // If all is ok, return full path of HTML template file and HTML filename without extension
+ return html_file, file_name, res_checksum_content
+}
+
+// fn (mut DynamicTemplateManager) create_template_cache_and_display_html(CacheRequest, i64, i64, string, string, i64, &map[string]DtmMultiTypeMap) return string
+//
+// Exclusively invoked from `serve_dynamic_template`.
+// It role is generate the HTML rendering of a template and relaying informations
+// to the cache manager for either the creation or updating of the HTML cache.
+// It begin to starts by ensuring that the cache delay expiration is correctly set by user.
+// It then parses the HTML file, replacing placeholders with actual dynamics/statics values.
+// If caching is enabled (indicated by a cache delay expiration different from -1) and the HTML content is valid,
+// the function constructs a `TemplateCache` request with all the necessary details.
+// This request is then sent to the cache handler channel, signaling either the need for a new cache or an update to an existing one.
+// The function returns the rendered HTML string immediately, without waiting for the cache to be created or updated.
+//
+fn (mut tm DynamicTemplateManager) create_template_cache_and_display_html(tcs CacheRequest, last_template_mod i64, unique_time i64, file_path string, tmpl_name string, cache_delay_expiration i64, placeholders &map[string]DtmMultiTypeMap, current_content_checksum string) string {
+ // Control if cache delay expiration is correctly setted. See the function itself for more details.
+ check_if_cache_delay_iscorrect(cache_delay_expiration, tmpl_name) or {
+ eprintln(err)
+ return dtm.internat_server_error
+ }
+ // Parses the HTML and stores the rendered output in the variable. See the function itself for more details.
+ mut html := tm.parse_html_file(file_path, tmpl_name, placeholders, tm.compress_html)
+ // If caching is enabled and the HTML content is valid, this section creates a temporary cache file, which is then used by the cache manager.
+ // If successfully temporary is created, a cache creation/update notification is sent through its dedicated channel to the cache manager
+ if cache_delay_expiration != -1 && html != dtm.internat_server_error && tm.active_cache_server {
+ op_success, tmp_name := tm.create_temp_cache(html, file_path, unique_time)
+ if op_success {
+ tm.ch_cache_handler <- TemplateCache{
+ id: tm.id_counter
+ name: tmpl_name
+ // 'path' field contains the full path, name and file extension of targeted HTML template.
+ path: file_path
+ content_checksum: current_content_checksum
+ tmp_name_file: tmp_name
+ // Last modified timestamp of HTML template
+ last_template_mod: last_template_mod
+ // Unix current local timestamp of cache generation request converted to UTC
+ generate_at: unique_time
+ // Defines the cache expiration delay in seconds. This value is added to 'generate_at' to calculate the expiration time of the cache.
+ cache_delay_expiration: cache_delay_expiration
+ // The requested routing to define creation or updating cache.
+ cache_request: tcs
+ }
+ // 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."
+ if tcs == .update || tcs == .exp_update {
+ tm.remaining_template_request(false, tm.id_to_handlered)
+ }
+ }
+ } else {
+ // In the context of an HTML validity error, the 'nbr_of_remaining_request' counter is consistently updated to avoid anomalies in cache management.
+ tm.remaining_template_request(false, tm.id_to_handlered)
+ }
+
+ return html
+}
+
+// fn (DynamicTemplateManager) create_temp_cache(&string, string, i64) return (bool, string)
+//
+// This function is responsible for creating a temporary cache file, which is subsequently used exclusively by the cache manager.
+// It generates a temporary file name using a checksum based on the timestamp and the file path.
+// The content is then written to this file, located in the designated cache folder.
+// If the operation is successful, a boolean is returned to allow for the sending of a create or modify request to the cache manager.
+//
+fn (tm DynamicTemplateManager) create_temp_cache(html &string, f_path string, ts i64) (bool, string) {
+ // Extracts the base file name with extension from the given file path.
+ file_name_with_ext := os.base(f_path)
+ // Removes the file extension, keeping only the name.
+ file_name := file_name_with_ext.all_before_last('.')
+ // Combines the timestamp and file path into a single string
+ combined_str := ts.str() + f_path
+ // Generates a md5 hash of the combined string for uniqueness
+ tmp_checksum := md5.hexhash(combined_str)
+ // Forms the temporary file name using the file name, checksum, and a .tmp extension
+ tmp_name := '${file_name}_${tmp_checksum}.tmp'
+ // Creates the full path for the temporary file in the cache folder
+ cache_path := os.join_path(tm.template_cache_folder, tmp_name)
+ // Converts the HTML content into a byte array
+ html_bytes := html.bytes()
+ mut f := os.create(cache_path) or {
+ eprintln('${dtm.message_signature_error} Cannot create tempory cache file : ${err.msg()}')
+ return false, ''
+ }
+ f.write(html_bytes) or {
+ eprintln('${dtm.message_signature_error} Cannot write in temporary cache file : ${err.msg()}')
+ f.close()
+ return false, ''
+ }
+ f.close()
+ return true, tmp_name
+}
+
+// fn (mut DynamicTemplateManager) get_cache(string, string, &map[string]DtmMultiTypeMap) return string
+//
+// Exclusively invoked from `serve_dynamic_template', retrieves the rendered HTML from the cache.
+//
+fn (mut tm DynamicTemplateManager) get_cache(name string, path string, placeholders &map[string]DtmMultiTypeMap) string {
+ mut html := ''
+ // Lock the cache database for writing.
+ rlock tm.template_caches {
+ for value in tm.template_caches {
+ // If the cache for the specified HTML template is found, perform the following operations:
+ if value.path == path {
+ match value.cache_storage_mode {
+ .memory {
+ // Retrieve the HTML render from the memory cache and convert it to a string.
+ html = value.html_data.bytestr()
+ }
+ .disk {
+ r_b_html := os.read_bytes(value.cache_full_path_name) or {
+ eprintln('${dtm.message_signature_error} Get_cache() cannot read template cache file ${value.name} : ${err.msg()} ')
+ return dtm.internat_server_error
+ }
+ html = r_b_html.bytestr()
+ }
+ }
+ // Function is used to signal that the process has finished using the cache information. The 'nbr_of_remaining_request' counter is therefore updated."
+ tm.remaining_template_request(false, value.id)
+ return html
+ }
+ }
+ }
+
+ return html
+}
+
+// fn (mut DynamicTemplateManager) return_cache_info_isexistent(string) return (bool, int, string, i64, i64, i64, string)
+//
+// Exclusively used in 'serve_dynamic_template' to determine whether a cache exists for the provided HTML template.
+// If a cache exists, it returns the necessary information for its transformation. If not, it indicates the need to create a new cache.
+//
+fn (mut tm DynamicTemplateManager) return_cache_info_isexistent(tmpl_path string) (bool, int, string, i64, i64, i64, string) {
+ // Lock the cache database for writing.
+ rlock tm.template_caches {
+ for value in tm.template_caches {
+ if value.path == tmpl_path {
+ // This code section handles cache redirection.
+ // If a cache redirection ID is found, it indicates that the currently used cache is outdated and there's a newer version available.
+ // The process then seeks to retrieve information from this more recent cache.
+ // 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.
+ // This recursive mechanism ensures that the latest cache data is always used.
+ if value.id_redirection != 0 {
+ mut need_goto := false
+ mut id_value_recursion := value.id_redirection
+ unsafe {
+ re_loop:
+ inner_loop: for val in tm.template_caches {
+ if val.id == id_value_recursion {
+ if val.id_redirection != 0 {
+ id_value_recursion = val.id_redirection
+ need_goto = true
+ break inner_loop
+ } else {
+ // function is used to signal that the process has begun using the cache information.
+ tm.remaining_template_request(true, val.id)
+ return true, val.id, val.path, val.last_template_mod, val.generate_at, val.cache_delay_expiration, val.content_checksum
+ }
+ }
+ }
+ if need_goto {
+ need_goto = false
+ goto re_loop
+ }
+ }
+ // No cache redirection, get cache current informations.
+ } else {
+ // function is used to signal that the process has begun using the cache information.
+ tm.remaining_template_request(true, value.id)
+ return true, value.id, value.path, value.last_template_mod, value.generate_at, value.cache_delay_expiration, value.content_checksum
+ }
+ }
+ }
+ }
+ // No existing cache, need to create it.
+ return false, 0, '', 0, 0, 0, ''
+}
+
+// fn (mut DynamicTemplateManager) remaining_template_request(bool, int)
+//
+// This function manages the counter in 'nbr_of_remaining_template_request', which tracks the number of requests that have started or finished for a specific cache.
+// It updates this information accordingly.
+// Moreover, this function sends a cache deletion callback request when the cache manager had previously been instructed to delete the cache but was unable to do because,
+// it was still in use.
+//
+fn (mut tm DynamicTemplateManager) remaining_template_request(b bool, v int) {
+ // Lock the remaining template request process for reading and writing.
+ lock tm.nbr_of_remaining_template_request {
+ for key, r_request in tm.nbr_of_remaining_template_request {
+ if r_request.id == v {
+ if b == true {
+ // if true, indicating a new request for the cache, Increments the count of active requests.
+ tm.nbr_of_remaining_template_request[key].nbr_of_remaining_request += 1
+ } else {
+ // if false, Decrements the count of active cache requests.
+ tm.nbr_of_remaining_template_request[key].nbr_of_remaining_request -= 1
+ // Checks if the number of active requests is zero or less and if there's a pending delete request for the cache.
+ // If yes, request is sent to the cache handler on this own channel.
+ if r_request.nbr_of_remaining_request <= 0
+ && r_request.need_to_send_delete_request {
+ tm.ch_cache_handler <- TemplateCache{
+ id: r_request.id
+ cache_request: .delete
+ }
+ }
+ }
+
+ break
+ }
+ }
+ }
+}
+
+// fn (mut DynamicTemplateManager) cache_handler()
+//
+// This function serves as the core handler for managing the cache within the DTM.
+// It continuously listens for cache update requests and processes them accordingly.
+// When a cache request is received, it either creates or updates or deletes the cache based on the request type.
+// The function ensures that duplicate cache requests are avoided and handles the closing of the cache handler.
+// It is necessary to restart the entire application in case the manager closes.
+// The manager handles cache operations in a multithreaded context, accepting up to a list of 200 operation requests. (Define in 'cache_handler_channel_cap' constant)
+// The HTML rendering is stored as a u8 array.
+//
+// TODO - Currently, the cache manager stops when it encounters an internal error requiring a restart of the program.
+// ( it is designed to ignore external errors since these are already handled in a way that ensures no cache processing requests are affected ),
+// A recovery system will need to be implemented to ensure service continuity.
+//
+fn (mut tm DynamicTemplateManager) cache_handler() {
+ defer {
+ // If cause is an internal cache handler error
+ tm.active_cache_server = false
+ // Close channel if handler is stopped
+ tm.ch_cache_handler.close()
+ tm.ch_stop_dtm_clock <- true
+ }
+ for {
+ select {
+ // Continuously listens until a request is received through the dedicated channel.
+ mut tc := <-tm.ch_cache_handler {
+ // Close handler if asked.
+ if tm.close_cache_handler {
+ eprintln('${dtm.message_signature_info} Cache manager has been successfully stopped. Please consider restarting the application if needed.')
+ break
+ }
+ f_path_tmp := os.join_path(tm.template_cache_folder, tc.tmp_name_file)
+
+ // determine if the requests passed to the manager are not duplicate requests. If so, the temporary file will be destroyed as part of a cache creation/update request.
+ if !tm.chandler_prevent_cache_duplicate_request(tc) {
+ if tc.cache_request != .delete {
+ // Determines the size of the template content to decide where to store it (in memory or on disk).
+ // It retrieves the content from the temporary file and then forms a unique checksum for the final name of the cache file.
+ // The file size is compared to the maximum allowable data size in memory.
+ // If the size is within the limit, the cache is stored in memory; otherwise, it's stored on disk.
+ // The unique checksum is generated by hashing the file data, its path, and the cache generation timestamp using md5.
+ tmp_file_size := os.file_size(f_path_tmp)
+ if tmp_file_size <= (tm.max_size_data_in_memory * 1024) {
+ tc.cache_storage_mode = .memory
+ } else {
+ tc.cache_storage_mode = .disk
+ }
+ file_data := os.read_bytes(f_path_tmp) or {
+ eprintln('${dtm.message_signature_error} Cache Handler : Failed to read tmp file, cache server will be stopped, you need to fix and restart application: ${err.msg()}')
+ break
+ }
+
+ combined_str := file_data.str() + tc.path + tc.generate_at.str()
+ tc.checksum = md5.hexhash(combined_str)
+
+ match tc.cache_storage_mode {
+ .memory {
+ // If the cache is stored in memory, the temporary file is destroyed.
+ tc.html_data = file_data
+ os.rm(f_path_tmp) or {
+ eprintln('${dtm.message_signature_error} Cache Handler : While deleting the tmp cache file: "${f_path_tmp}", cache server will be stopped, you need to fix and restart application: ${err.msg()}')
+ break
+ }
+ }
+ .disk {
+ // If the cache is stored on disk, the temporary file is renamed to become the definitive cache of the current version of the HTML template.
+ tc.cache_full_path_name = os.join_path(tm.template_cache_folder,
+ '${tc.name}_${tc.checksum}.cache')
+ os.mv(f_path_tmp, tc.cache_full_path_name) or {
+ eprintln('${dtm.message_signature_error} Cache Handler : Failed to rename tmp file, cache server will be stopped, you need to fix and restart application: ${err.msg()}')
+ break
+ }
+ }
+ }
+ }
+ // Lock the cache database for reading and writing.
+ lock tm.template_caches {
+ if tc.cache_request != .delete {
+ // Include Cache information in database.
+ tm.template_caches << tc
+ }
+ if tc.cache_request == .new {
+ tm.chandler_remaining_cache_template_used(tc.cache_request,
+ tc.id, tm.id_to_handlered)
+ // Increment ID counter for the next creation/update cache request
+ tm.id_counter++
+ } else {
+ if tc.cache_request != .delete {
+ tm.id_counter++
+ }
+ // This function allows the cache manager to handle what happens in 'nbr_of_remaining_template_request' and
+ // act accordingly for the creation, update, or destruction of the cache.
+ test_b := tm.chandler_remaining_cache_template_used(tc.cache_request,
+ tc.id, tm.id_to_handlered)
+ if test_b {
+ // Finding position of cache in database ( If disk mode, cache is erased )
+ key, is_success := tm.chandler_clear_specific_cache(tm.id_to_handlered)
+ if !is_success {
+ break
+ }
+ // Delete in database.
+ tm.template_caches.delete(key)
+ }
+ }
+ }
+ } else if tc.cache_request != .delete {
+ os.rm(f_path_tmp) or {
+ eprintln('${dtm.message_signature_warn} Cache Handler : Cannot deleting the unused tmp cache file: "${f_path_tmp}" : ${err.msg()}')
+ }
+ }
+ }
+ }
+ }
+}
+
+// fn (DynamicTemplateManager) chandler_prevent_cache_duplicate_request(&TemplateCache) return bool
+//
+// Exclusively used by the cache handler, assesses whether a cache request is a duplicate,
+// based on the type of cache request (.new, .update, .exp_update, .delete) and the existing data.
+// Returns true to indicate a duplicate request, which will be ignored, and false otherwise.
+//
+fn (tm DynamicTemplateManager) chandler_prevent_cache_duplicate_request(tc &TemplateCache) bool {
+ match tc.cache_request {
+ .new {
+ for value in tm.template_caches {
+ // Evaluate full path
+ if value.path == tc.path {
+ return true
+ }
+ }
+ }
+ .update {
+ for value in tm.template_caches {
+ // Evaluate full path + The timestamp of the last html file modification + checksum of content.
+ if value.path == tc.path && value.last_template_mod == tc.last_template_mod
+ && value.content_checksum == tc.content_checksum {
+ return true
+ }
+ }
+ }
+ .exp_update {
+ for value in tm.template_caches {
+ // Evaluate full path + The timestamp of the last html modification file +
+ // Checks if the current cache generation time is within the expiration window set from the last update.
+ if value.path == tc.path && value.last_template_mod == tc.last_template_mod {
+ if tc.generate_at < (value.generate_at + value.cache_delay_expiration) {
+ return true
+ }
+ }
+ }
+ }
+ .delete {
+ for value in tm.template_caches {
+ // Evaluate ID to test if cache is always in database.
+ if value.id == tc.id {
+ return false
+ }
+ }
+ // Otherwise cache has already been deleted
+ return true
+ }
+ else {}
+ }
+
+ return false
+}
+
+// fn (mut DynamicTemplateManager) chandler_clear_specific_cache(int) return (int, bool)
+//
+// Exclusively associated with the cache handler, is used to remove specific cache information from the database when necessary.
+// It identifies the target 'TemplateCache' by its id in the array database and deletes its corresponding cache file in 'disk mode'.
+//
+fn (mut tm DynamicTemplateManager) chandler_clear_specific_cache(id int) (int, bool) {
+ for key, value in tm.template_caches {
+ if value.id == id {
+ match value.cache_storage_mode {
+ .memory {}
+ .disk {
+ file_path := os.join_path(tm.template_cache_folder, '${value.name}_${value.checksum}.cache')
+ os.rm(file_path) or {
+ eprintln('${dtm.message_signature_error} While deleting the specific cache file: ${file_path}, cache server will be stopped, you need to fix and restart application: : ${err.msg()}')
+ break
+ }
+ }
+ }
+ return key, true
+ }
+ }
+ return 0, false
+}
+
+// fn (mut DynamicTemplateManager) chandler_remaining_cache_template_used(CacheRequest, int, int) return bool
+//
+// Exclusively associated with the cache handler for managing the lifecycle of cache requests in 'nbr_of_remaining_template_request'.
+// For each cache request (creation, update, or deletion), it updates the status of ongoing requests and decides on necessary actions.
+// 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.
+// If false, it indicates that the cache cannot yet be destroyed due to ongoing usage.
+//
+fn (mut tm DynamicTemplateManager) chandler_remaining_cache_template_used(cr CacheRequest, id int, old_id int) bool {
+ lock tm.nbr_of_remaining_template_request {
+ match cr {
+ // Adds a new request in 'nbr_of_remaining_template_request' to track the usage of the newly created cache
+ .new {
+ tm.nbr_of_remaining_template_request << RemainingTemplateRequest{
+ id: id
+ }
+ }
+ .update, .exp_update {
+ // Marks the old cache as obsolete and adds a request for the new cache.
+ // 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
+ for key, mut value in tm.nbr_of_remaining_template_request {
+ if value.id == old_id {
+ value.need_to_delete = true
+ tm.nbr_of_remaining_template_request << RemainingTemplateRequest{
+ id: id
+ }
+ // If possible, immediately deleted request
+ if value.nbr_of_remaining_request <= 0 && value.need_to_delete == true {
+ tm.nbr_of_remaining_template_request.delete(key)
+ return true
+ // else, set for deferred deletion of the cache as soon as feasible
+ } else {
+ value.need_to_send_delete_request = true
+ }
+ break
+ }
+ }
+ return false
+ }
+ .delete {
+ // Removes the cache request from the list if it's no longer in use.
+ for key, value in tm.nbr_of_remaining_template_request {
+ if value.id == id {
+ if value.nbr_of_remaining_request <= 0 && value.need_to_delete == true {
+ tm.nbr_of_remaining_template_request.delete(key)
+ }
+
+ break
+ }
+ }
+ }
+ else {}
+ }
+ }
+ return true
+}
+
+// fn (mut DynamicTemplateManager) stop_cache_handler()
+//
+// Signals the termination of the cache handler by setting 'close_cache_handler' to true and sending a signal through the channel.
+//
+fn (mut tm DynamicTemplateManager) stop_cache_handler() {
+ if tm.active_cache_server {
+ tm.active_cache_server = false
+ tm.close_cache_handler = true
+ tm.ch_cache_handler <- TemplateCache{
+ id: 0
+ }
+ }
+}
+
+// fn (mut DynamicTemplateManager) parse_html_file(string, string, &map[string]DtmMultiTypeMap, bool) return (string, string)
+//
+// The V compiler's template parser 'vlib/v/parser/tmpl.v', parses and transforms HTML template file content.
+// It ensures HTML format compatibility necessary for proper compilation and execution in its typical usage outside of DTM like managing various states,
+// processing template tags, and supporting string interpolation...
+// This function checks for the presence and validity of template directives '@include'
+// to prevent runtime errors related to incorrect inclusion paths. Replaces placeholders with their actual values,
+// 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 ).
+//
+// TODO - This function does not perform an in-depth check to ensure the file is indeed a valid HTML file, which could lead to possible runtime crashes,
+// Addressing this by adding a control mechanism is recommended for enhanced stability.
+// For now, it is the user's responsibility to provide the correct HTML file(s) and HTML file(s)-content format.
+//
+const allowed_tags = ['', '
', '', '
', '', '
', '', '
', '',
+ '
', '', '
', '', '
', '', '
', '
', '
', '', '',
+ '', '', '
', '', '', '', '
', '', '', '',
+ '', '', '', '', '', '', ' | ',
+ '', '
', '', ' | ', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', ' ', '', '', '', '',
+ '']
+
+const include_html_key_tag = '_#includehtml'
+
+fn (mut tm DynamicTemplateManager) parse_html_file(file_path string, tmpl_name string, placeholders &map[string]DtmMultiTypeMap, is_compressed bool) string {
+ // To prevent runtime crashes related to template include directives error,
+ // this code snippet ensures that the paths in include directives '@include' are correct.
+ html_content := os.read_file(file_path) or {
+ eprintln("${dtm.message_signature_error} Unable to read the file: '${file_path}' with HTML parser function.")
+ return dtm.internat_server_error
+ }
+ mut re := regex.regex_opt('.*@include(?P[ \t\r\n]+)(?P[\'"])(?P.*)(?P[\'"])') or {
+ tm.stop_cache_handler()
+ eprintln('${dtm.message_signature_error} with regular expression for template @inclusion in parse_html_file() function. Please check the syntax of the regex pattern : ${err.msg()}')
+ return dtm.internat_server_error
+ }
+ // Find all occurrences of the compiled regular expression within the HTML content.
+ matches := re.find_all(html_content)
+ // Check if any matches were found.
+ if matches.len > 0 {
+ mut full_path := ''
+ // Iterate through the matches. Since each match has a start and end index, increment by 2 for each iteration.
+ for i := 0; i < matches.len; i += 2 {
+ // Retrieve the start and end indices of the current match.
+ start := matches[i]
+ end := matches[i + 1]
+ // Extract the substring from the HTML content that corresponds to the current match.
+ match_text := html_content[start..end]
+ // Apply the regex to the extracted substring to enable group capturing.
+ re.match_string(match_text)
+ // Extract the path from the current match.
+ mut path := re.get_group_by_name(match_text, 'path')
+ // Extract the type of quotation marks.
+ quote_type_beg := re.get_group_by_name(match_text, 'quote_type_beg')
+ quote_type_end := re.get_group_by_name(match_text, 'quote_type_end')
+ // Extract the whitespace characters following the '@include' directive.
+ space_type := re.get_group_by_name(match_text, 'space_type')
+ // Check if double quotes are used or if the whitespace sequence contains newline (\n) or carriage return (\r) characters
+ if quote_type_beg == '"' || quote_type_end == '"' || space_type.contains('\n')
+ || space_type.contains('\r') {
+ eprintln("${dtm.message_signature_error} In the HTML template: '${file_path}', an error occurred in one of the '@include' directives. This could be due to the use of double quotes or unexpected newline/carriage return characters in the whitespace.")
+ return dtm.internat_server_error
+ }
+ // Check if the 'path' string does not end with '.html'. If it doesn't, append '.html' to ensure the path has the correct extension.
+ if !path.ends_with('.html') {
+ path += '.html'
+ }
+ full_path = os.join_path(tm.template_folder, path)
+ // Check if the 'path' is empty or if the HTML template path does not exist.
+ if path.len < 1 || !os.exists(full_path) {
+ eprintln("${dtm.message_signature_error} In the HTML template: '${file_path}', an error occurred in one of the '@include' directives. This could be due to the use of an invalid path: ${full_path}")
+ return dtm.internat_server_error
+ }
+ }
+ }
+ mut p := parser.Parser{}
+ // Parse/transform the HTML content, and a subsequent cleaning function restores the parsed content to a usable state for the DTM.
+ // Refer to the comments in 'clean_parsed_html' for details.
+ mut html := tm.clean_parsed_html(p.compile_template_file(file_path, tmpl_name), tmpl_name,
+ placeholders)
+ // If clean_parsed_html() return error
+ if html == dtm.internat_server_error {
+ return html
+ }
+ // This section completes the processing by replacing any placeholders that were not handled by the compiler template parser.
+ // If there are placeholders present, it iterates through them, applying necessary filters and substituting their values into the HTML content.
+ // dtm.filter function used here. 'escape_html_strings_in_templates.v'
+ // Checks if there are any placeholders to process
+ if placeholders.len > 0 {
+ for key, value in placeholders {
+ mut val := ''
+ mut key_m := key
+ match value {
+ i8, i16, int, i64, u8, u16, u32, u64, f32, f64 {
+ // Converts value to string
+ temp_val := value.str()
+ // Filters the string value for safe HTML insertion
+ val = filter(temp_val)
+ }
+ string {
+ // Checks if the placeholder allows HTML inclusion
+ if key.ends_with(dtm.include_html_key_tag) {
+ // Iterates over allowed HTML tags for inclusion
+ for tag in dtm.allowed_tags {
+ // Escapes the HTML tag
+ escaped_tag := filter(tag)
+ // Replaces the escaped tags with actual HTML tags in the value
+ val = value.replace(escaped_tag, tag)
+ }
+ // Adjusts the placeholder key by removing the HTML inclusion tag
+ key_m = key.all_before_last(dtm.include_html_key_tag)
+ } else {
+ // Filters the string value for safe HTML insertion
+ val = filter(value)
+ }
+ }
+ }
+ // Forms the actual placeholder to be replaced in the HTML.
+ placeholder := '$${key_m}'
+ // Check if placeholder exist in the HTML
+ if html.contains(placeholder) {
+ // Replaces the placeholder if exist in the HTML with the actual value.
+ html = html.replace(placeholder, val)
+ }
+ }
+ }
+
+ // Performs a light compression of the HTML output by removing usless spaces, newlines, and tabs if user selected this option.
+ if is_compressed {
+ html = html.replace_each(['\n', '', '\t', '', ' ', ' '])
+ mut r := regex.regex_opt(r'>(\s+)<') or {
+ tm.stop_cache_handler()
+ eprintln('${dtm.message_signature_error} with regular expression for HTML light compression in parse_html_file() function. Please check the syntax of the regex pattern : ${err.msg()}')
+ return dtm.internat_server_error
+ }
+ html = r.replace(html, '><')
+ for html.contains(' ') {
+ html = html.replace(' ', ' ')
+ }
+ }
+
+ return html
+}
+
+// fn (mut DynamicTemplateManager) clean_parsed_html(string, string) return string
+//
+// Is specifically designed to clean the HTML output generated by V's compiler template parser.
+// It addresses the fact that the parser prepares HTML content for integration into an executable, rather than for direct use in a web browser.
+// The function adjusts markers and escapes specific characters to convert the parser's output into a format suitable for web browsers.
+//
+// TODO - Any changes to the HTML output made by the V lang compiler template parser in 'vlib/v/parser/tmpl.v'
+// may necessitate corresponding adjustments in this function. Perhaps a more independent function is needed to clean the HTML rendering?
+//
+// TODO - This function does not currently handle the cleanup of all template directives typically managed by the Vlang compiler, such as conditional statements or loops....
+// Implementation of these features will be necessary.
+//
+fn (mut tm DynamicTemplateManager) clean_parsed_html(tmpl string, tmpl_name string, provided_placeholders &map[string]DtmMultiTypeMap) string {
+ // Defines the start marker to encapsulate HTML content
+ start_marker := "sb_${tmpl_name}.write_string('"
+ // Determines the end marker, signaling the end of HTML content
+ end_marker := "')\n\n\t_tmpl_res_${tmpl_name} := sb_${tmpl_name}.str()"
+ // Searches for the start marker in the processed HTML content. Triggers an error if the start marker is not found.
+ start := tmpl.index(start_marker) or {
+ eprintln("${dtm.message_signature_error} Start marker not found for '${tmpl_name}': ${err.msg()}")
+ // dtm.filter function used here. 'escape_html_strings_in_templates.v'
+ return filter(tmpl)
+ }
+ // Identifies the last occurrence of the end marker. Signals an error if it is missing.
+ end := tmpl.index_last(end_marker) or {
+ eprintln("${dtm.message_signature_error} End marker not found for '${tmpl_name}': ${err.msg()}")
+ // dtm.filter function used here. 'escape_html_strings_in_templates.v'
+ return filter(tmpl)
+ }
+ // Extracts the portion of HTML content between the start and end markers.
+ mut html := tmpl[start + start_marker.len..end]
+
+ // Utilizes a regular expression to identify placeholders within the HTML template output.
+ // This process checks for placeholders that have no corresponding entries in the provided placeholders map.
+ // Any unmatched placeholders found are then safely escaped
+ mut r := regex.regex_opt(r'$([a-zA-Z_][a-zA-Z0-9_]*)') or {
+ tm.stop_cache_handler()
+ eprintln('${dtm.message_signature_error} with regular expression for identifying placeholders in the HTML template in clean_parsed_html() function. Please check the syntax of the regex pattern : ${err.msg()}')
+ return dtm.internat_server_error
+ }
+ indices := r.find_all(html)
+ // Iterates through each found placeholder.
+ for i := 0; i < indices.len; i += 2 {
+ // Retrieves the start and end indices of the current placeholder.
+ beginning := indices[i]
+ ending := indices[i + 1]
+ // Extracts the placeholder from the HTML using the indices.
+ placeholder := html[beginning..ending]
+ // Removes the '$' symbol to get the placeholder name.
+ placeholder_name := placeholder[1..]
+ // Checks if the placeholder or its variant with '_#includehtml' is not in the provided placeholders map.
+ if !(placeholder_name in provided_placeholders
+ || (placeholder_name + dtm.include_html_key_tag) in provided_placeholders) {
+ // If so, escapes the unresolved placeholder and replaces the original placeholder with the escaped version
+ escaped_placeholder := filter(placeholder)
+ html = html.replace(placeholder, escaped_placeholder)
+ }
+ }
+
+ // Transforms parser-specific escape sequences into their respective characters for proper HTML
+ html = html.replace('\\n', '\n').replace("\\'", "'")
+ return html
+}
+
+// fn check_if_cache_delay_iscorrect(i64, string) return !
+//
+// Validates the user-specified cache expiration delay for HTML templates.
+// It enforces three permissible delay settings:
+// - A minimum of five minutes and a maximum of one year for standard cache expiration. ( Define in constants )
+// - A parameter of 0 for an infinite cache expiration delay
+// - A parameter of -1 for no caching, meaning the HTML template is processed every time without being stored in the cache."
+//
+fn check_if_cache_delay_iscorrect(cde i64, tmpl_name string) ! {
+ if (cde != 0 && cde != -1 && cde < dtm.converted_cache_delay_expiration_at_min)
+ || (cde != 0 && cde != -1 && cde > dtm.converted_cache_delay_expiration_at_max) {
+ return error("${dtm.message_signature_error} The cache timeout for template '${tmpl_name}.html' cannot be set to a value less than '${dtm.cache_delay_expiration_at_min}' seconds and more than '${dtm.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.")
+ }
+}
+
+// fn cache_request_route(bool, i64, i64, i64, i64, i64, i64) return (CacheRequest, i64)
+//
+// Used exclusively in 'serve_dynamic_template' function, determines the appropriate cache request action for an HTML template.
+// It assesses various conditions such as cache existence, cache expiration settings, and last modification timestamps ( template or dynamic content )
+// to decide whether to create a new cache, update an existing or delivered a valid cache content.
+//
+fn (mut tm DynamicTemplateManager) cache_request_route(is_cache_exist bool, neg_cache_delay_expiration i64, last_template_mod i64, test_current_template_mod i64, cache_del_exp i64, gen_at i64, c_time i64, content_checksum string, current_content_checksum string) (CacheRequest, i64) {
+ if !is_cache_exist || neg_cache_delay_expiration == -1 {
+ // Requiere cache creation
+ unique_ts := get_current_unix_micro_timestamp()
+ tm.c_time = unique_ts
+ return CacheRequest.new, unique_ts
+ } else if last_template_mod < test_current_template_mod
+ || content_checksum != current_content_checksum {
+ // Requires cache update as the HTML template has been modified since the last time. it can be the template itself or its dynamic content.
+ unique_ts := get_current_unix_micro_timestamp()
+ tm.c_time = unique_ts
+ return CacheRequest.update, unique_ts
+ } else if cache_del_exp != 0 && (gen_at + cache_del_exp) < tm.c_time {
+ unique_ts := get_current_unix_micro_timestamp()
+ tm.c_time = unique_ts
+ // Requires cache update as the cache expiration delay has elapsed.
+ return CacheRequest.exp_update, unique_ts
+ } else {
+ // Returns valid cached content, no update or creation necessary.
+ return CacheRequest.cached, 0
+ }
+}
+
+// fn (mut tm DynamicTemplateManager) handle_dtm_clock()
+//
+// Manages the internal clock. It periodically updates ( 4 minutes minimum, if there has been a recent update elsewhere in the system, it will be taken into account by the clock handler. )
+// The goal is ensuring that the DTM's internal time is maintained, especially during prolonged periods of inactivity on the website,
+// this can potentially lead to cache expiration issues if there is zero traffic for a while."
+//
+
+// Minimum update interval ( in seconds ) set to 4 minutesminimum_wait_time_until_next_update
+const update_duration = 240
+
+fn (mut tm DynamicTemplateManager) handle_dtm_clock() {
+ defer {
+ tm.ch_stop_dtm_clock.close()
+ eprintln('${dtm.message_signature_info} DTM clock handler has been successfully stopped.')
+ }
+
+ for {
+ // Calculate the remaining time until the next update.
+ current_time := get_current_unix_micro_timestamp() / dtm.convert_seconds
+ mut time_since_last_update := int(current_time - (tm.c_time / dtm.convert_seconds))
+ mut minimum_wait_time_until_next_update := dtm.update_duration
+
+ // Update DTM clock if update interval exceeded otherwise, set next check based on time since last update
+ if time_since_last_update >= dtm.update_duration {
+ tm.c_time = current_time * dtm.convert_seconds
+ } else {
+ if time_since_last_update < 0 {
+ time_since_last_update = 0
+ }
+ minimum_wait_time_until_next_update = (dtm.update_duration - time_since_last_update) +
+ dtm.update_duration
+ }
+
+ // Wait until the next update interval or until a stop signal is received.
+ for elapsed_time := 0; elapsed_time < minimum_wait_time_until_next_update; elapsed_time++ {
+ select {
+ _ := <-tm.ch_stop_dtm_clock {
+ break
+ }
+ else {
+ // Attendre une seconde
+ time.sleep(1 * time.second)
+ }
+ }
+ }
+ // Reset wait time for next cycle.
+ minimum_wait_time_until_next_update = dtm.update_duration
+ }
+}
+
+// fn get_current_unix_timestamp() return i64
+//
+// This function is designed for handling timezone adjustments by converting the machine's local time at micro format to a universal micro format.
+//
+fn get_current_unix_micro_timestamp() i64 {
+ return time.now().unix_time_micro()
+}
diff --git a/vlib/x/templating/dtm/dynamic_template_manager_test.v b/vlib/x/templating/dtm/dynamic_template_manager_test.v
new file mode 100644
index 0000000000..efb8eb642a
--- /dev/null
+++ b/vlib/x/templating/dtm/dynamic_template_manager_test.v
@@ -0,0 +1,405 @@
+module dtm
+
+import os
+import time
+
+const temp_dtm_dir = 'dynamic_template_manager_test'
+const temp_cache_dir = 'vcache'
+const temp_templates_dir = 'templates'
+const temp_html_fp = 'temp.html'
+const temp_html_n = 'temp'
+const vtmp_dir = os.vtmp_dir()
+
+fn testsuite_begin() {
+ temp_folder := os.join_path(dtm.vtmp_dir, dtm.temp_dtm_dir)
+ os.mkdir_all(temp_folder)!
+
+ vcache_path := os.join_path(temp_folder, dtm.temp_cache_dir)
+ templates_path := os.join_path(temp_folder, dtm.temp_templates_dir)
+
+ os.mkdir_all(vcache_path)!
+ os.mkdir_all(templates_path)!
+
+ temp_html_file := os.join_path(templates_path, dtm.temp_html_fp)
+
+ html_content := '
+
+
+
+ TEST
+
+
+
+
TEST
+
+
+ '
+
+ os.write_file(temp_html_file, html_content)!
+}
+
+fn test_initialize_dtm() {
+ dtmi := init_dtm(false, 0)!
+ assert dtmi.dtm_init_is_ok == true
+}
+
+fn test_check_and_clear_cache_files() {
+ dtmi := init_dtm(false, 0)!
+ dtmi.check_and_clear_cache_files()!
+
+ count_cache_files := os.ls(dtmi.template_cache_folder)!
+ assert count_cache_files.len == 0
+}
+
+fn test_create_template_cache_and_display_html() {
+ mut dtmi := init_dtm(true, max_size_data_in_memory)!
+ defer {
+ dtmi.stop_cache_handler()
+ }
+ html := dtmi.create_cache()
+ assert html.len > 10
+}
+
+fn test_get_cache() {
+ mut dtmi := init_dtm(true, max_size_data_in_memory)!
+ dtmi.create_cache()
+ defer {
+ dtmi.stop_cache_handler()
+ }
+ dtm_placeholers := map[string]DtmMultiTypeMap{}
+ temp_html_file := os.join_path(dtmi.template_folder, dtm.temp_html_fp)
+ html_mem := dtmi.get_cache(dtm.temp_html_n, temp_html_file, &dtm_placeholers)
+ assert html_mem.len > 10
+}
+
+fn test_return_cache_info_isexistent() {
+ mut dtmi := init_dtm(false, 0)!
+ path_template := os.join_path(dtmi.template_folder, dtm.temp_html_fp)
+ lock dtmi.template_caches {
+ dtmi.template_caches << TemplateCache{
+ id: 1
+ path: path_template
+ }
+ }
+ lock dtmi.nbr_of_remaining_template_request {
+ dtmi.nbr_of_remaining_template_request << RemainingTemplateRequest{
+ id: 1
+ }
+ }
+ cache_exists, _, _, _, _, _, _ := dtmi.return_cache_info_isexistent(path_template)
+ assert cache_exists == true
+ lock dtmi.template_caches {
+ dtmi.template_caches[0].id_redirection = 2
+ dtmi.template_caches << TemplateCache{
+ id: 2
+ path: path_template
+ id_redirection: 3
+ }
+ dtmi.template_caches << TemplateCache{
+ id: 3
+ path: path_template
+ id_redirection: 4
+ }
+ dtmi.template_caches << TemplateCache{
+ id: 4
+ path: path_template
+ id_redirection: 5
+ }
+ dtmi.template_caches << TemplateCache{
+ id: 5
+ path: path_template
+ }
+ }
+ lock dtmi.nbr_of_remaining_template_request {
+ dtmi.nbr_of_remaining_template_request << RemainingTemplateRequest{
+ id: 2
+ }
+ dtmi.nbr_of_remaining_template_request << RemainingTemplateRequest{
+ id: 3
+ }
+ dtmi.nbr_of_remaining_template_request << RemainingTemplateRequest{
+ id: 4
+ }
+ dtmi.nbr_of_remaining_template_request << RemainingTemplateRequest{
+ id: 5
+ }
+ }
+ _, id, _, _, _, _, _ := dtmi.return_cache_info_isexistent(path_template)
+ assert id == 5
+}
+
+fn test_remaining_template_request() {
+ mut dtmi := init_dtm(false, 0)!
+
+ lock dtmi.nbr_of_remaining_template_request {
+ dtmi.nbr_of_remaining_template_request << RemainingTemplateRequest{
+ id: 1
+ }
+ }
+ dtmi.remaining_template_request(true, 1)
+ rlock dtmi.nbr_of_remaining_template_request {
+ assert dtmi.nbr_of_remaining_template_request[0].nbr_of_remaining_request == 1
+ }
+ dtmi.remaining_template_request(true, 1)
+ rlock dtmi.nbr_of_remaining_template_request {
+ assert dtmi.nbr_of_remaining_template_request[0].nbr_of_remaining_request == 2
+ }
+ dtmi.remaining_template_request(false, 1)
+ rlock dtmi.nbr_of_remaining_template_request {
+ assert dtmi.nbr_of_remaining_template_request[0].nbr_of_remaining_request == 1
+ }
+ dtmi.remaining_template_request(false, 1)
+ rlock dtmi.nbr_of_remaining_template_request {
+ assert dtmi.nbr_of_remaining_template_request[0].nbr_of_remaining_request == 0
+ }
+}
+
+fn test_check_html_and_placeholders_size() {
+ mut dtmi := init_dtm(false, 0)!
+ temp_html_file := os.join_path(dtmi.template_folder, dtm.temp_html_fp)
+ placeholders := map[string]DtmMultiTypeMap{}
+
+ path, filename, content_checksum := dtmi.check_html_and_placeholders_size(temp_html_file,
+ &placeholders)!
+
+ assert path.len > 10
+ assert filename.len > 3
+ // assert content_checksum.len > 3
+}
+
+fn test_chandler_prevent_cache_duplicate_request() {
+ dtmi := init_dtm(false, 0)!
+ temp_html_file := os.join_path(dtmi.template_folder, dtm.temp_html_fp)
+
+ lock dtmi.template_caches {
+ dtmi.template_caches << TemplateCache{
+ id: 1
+ path: temp_html_file
+ cache_request: .new
+ }
+ dtmi.template_caches << TemplateCache{
+ id: 2
+ path: temp_html_file
+ cache_request: .update
+ last_template_mod: i64(1)
+ }
+ dtmi.template_caches << TemplateCache{
+ id: 3
+ path: temp_html_file
+ cache_request: .exp_update
+ last_template_mod: i64(1)
+ generate_at: i64(100)
+ }
+ dtmi.template_caches << TemplateCache{
+ id: 4
+ cache_request: .delete
+ }
+ }
+ new_cache := TemplateCache{
+ id: 5
+ path: temp_html_file
+ cache_request: .new
+ }
+ update_cache := TemplateCache{
+ id: 6
+ path: temp_html_file
+ cache_request: .update
+ last_template_mod: i64(1)
+ }
+ exp_update_cache := TemplateCache{
+ id: 7
+ path: temp_html_file
+ cache_request: .exp_update
+ last_template_mod: i64(1)
+ generate_at: i64(10)
+ cache_delay_expiration: i64(10)
+ }
+ delete_cache := TemplateCache{
+ id: 4
+ cache_request: .delete
+ }
+ mut is_duplicate := dtmi.chandler_prevent_cache_duplicate_request(&new_cache)
+ assert is_duplicate == true
+ is_duplicate = dtmi.chandler_prevent_cache_duplicate_request(&update_cache)
+ assert is_duplicate == true
+ is_duplicate = dtmi.chandler_prevent_cache_duplicate_request(&exp_update_cache)
+ assert is_duplicate == true
+ is_duplicate = dtmi.chandler_prevent_cache_duplicate_request(&delete_cache)
+ assert is_duplicate == false
+
+ lock dtmi.template_caches {
+ dtmi.template_caches.delete(3)
+ }
+
+ is_duplicate = dtmi.chandler_prevent_cache_duplicate_request(&delete_cache)
+ assert is_duplicate == true
+}
+
+fn test_chandler_clear_specific_cache() {
+ mut dtmi := init_dtm(true, 0)!
+ defer {
+ dtmi.stop_cache_handler()
+ }
+ dtmi.create_cache()
+ lock dtmi.template_caches {
+ cache_file := os.join_path(dtmi.template_cache_folder, '${dtmi.template_caches[0].name}_${dtmi.template_caches[0].checksum}.cache')
+ index, is_success := dtmi.chandler_clear_specific_cache(dtmi.template_caches[0].id)
+ assert is_success == true
+ assert index == 0
+ cache_exist := os.exists(cache_file)
+ assert cache_exist == false
+ }
+}
+
+fn test_chandler_remaining_cache_template_used() {
+ mut dtmi := init_dtm(false, 0)!
+ lock dtmi.nbr_of_remaining_template_request {
+ dtmi.nbr_of_remaining_template_request << RemainingTemplateRequest{
+ id: 1
+ nbr_of_remaining_request: 0
+ }
+ dtmi.nbr_of_remaining_template_request << RemainingTemplateRequest{
+ id: 2
+ nbr_of_remaining_request: 1
+ need_to_delete: true
+ }
+ dtmi.nbr_of_remaining_template_request << RemainingTemplateRequest{
+ id: 3
+ nbr_of_remaining_request: 0
+ need_to_delete: true
+ }
+ }
+ mut can_delete := dtmi.chandler_remaining_cache_template_used(CacheRequest.update,
+ 3, 3)
+ assert can_delete == true
+ can_delete = dtmi.chandler_remaining_cache_template_used(CacheRequest.update, 2, 2)
+ assert can_delete == false
+ can_delete = dtmi.chandler_remaining_cache_template_used(CacheRequest.delete, 1, 0)
+ assert can_delete == true
+ can_delete = dtmi.chandler_remaining_cache_template_used(CacheRequest.new, 4, 0)
+ assert can_delete == true
+}
+
+fn test_parse_html_file() {
+ mut dtmi := init_dtm(false, 0)!
+ temp_folder := os.join_path(dtm.vtmp_dir, dtm.temp_dtm_dir)
+ templates_path := os.join_path(temp_folder, dtm.temp_templates_dir)
+ temp_html_file := os.join_path(templates_path, dtm.temp_html_fp)
+
+ mut placeholders := map[string]DtmMultiTypeMap{}
+
+ is_compressed := true
+ html := dtmi.parse_html_file(temp_html_file, dtm.temp_html_n, &placeholders, is_compressed)
+
+ assert html.len > 0
+}
+
+fn test_check_if_cache_delay_iscorrect() {
+ check_if_cache_delay_iscorrect(i64(300 * 1000000), dtm.temp_html_n) or { assert false }
+
+ check_if_cache_delay_iscorrect(i64(-100), dtm.temp_html_n) or { assert true }
+}
+
+fn test_cache_request_route() {
+ mut dtmi := init_dtm(false, 0)!
+ mut is_cache_exist := true
+ mut cache_delay_expiration := i64(400)
+ mut last_template_mod := get_current_unix_micro_timestamp()
+ mut test_current_template_mod := last_template_mod
+ mut cache_del_exp := 300
+ mut gen_at := last_template_mod
+ mut content_checksum := 'checksumtest1'
+ mut current_content_checksum := 'checksumtest2'
+
+ mut request_type, _ := dtmi.cache_request_route(is_cache_exist, cache_delay_expiration,
+ last_template_mod, test_current_template_mod, cache_del_exp, gen_at, get_current_unix_micro_timestamp(),
+ content_checksum, current_content_checksum)
+
+ assert request_type == CacheRequest.update
+
+ current_content_checksum = 'checksumtest1'
+
+ request_type, _ = dtmi.cache_request_route(is_cache_exist, cache_delay_expiration,
+ last_template_mod, test_current_template_mod, cache_del_exp, gen_at, get_current_unix_micro_timestamp(),
+ content_checksum, current_content_checksum)
+
+ assert request_type == CacheRequest.cached
+
+ gen_at = (last_template_mod - 500)
+
+ request_type, _ = dtmi.cache_request_route(is_cache_exist, cache_delay_expiration,
+ last_template_mod, test_current_template_mod, cache_del_exp, gen_at, get_current_unix_micro_timestamp(),
+ content_checksum, current_content_checksum)
+
+ assert request_type == CacheRequest.exp_update
+
+ is_cache_exist = false
+
+ request_type, _ = dtmi.cache_request_route(is_cache_exist, cache_delay_expiration,
+ last_template_mod, test_current_template_mod, cache_del_exp, gen_at, get_current_unix_micro_timestamp(),
+ content_checksum, current_content_checksum)
+
+ assert request_type == CacheRequest.new
+}
+
+fn test_cache_handler() {
+ mut dtmi := init_dtm(true, max_size_data_in_memory)!
+ defer {
+ dtmi.stop_cache_handler()
+ }
+ dtmi.create_cache()
+ path_f := os.join_path(dtmi.template_folder, dtm.temp_html_fp)
+ lock dtmi.template_caches {
+ assert dtmi.template_caches[0].id == 1
+ assert dtmi.template_caches[0].name == dtm.temp_html_n
+ assert dtmi.template_caches[0].path == path_f
+ }
+ dtmi.id_to_handlered = 1
+ dtmi.ch_cache_handler <- TemplateCache{
+ id: 1
+ cache_request: .delete
+ }
+ time.sleep(5 * time.millisecond)
+ lock dtmi.template_caches {
+ assert dtmi.template_caches.len == 0
+ }
+}
+
+fn testsuite_end() {
+ temp_folder := os.join_path(dtm.vtmp_dir, dtm.temp_dtm_dir)
+ os.rmdir_all(temp_folder) or {}
+}
+
+// Utilities function :
+
+fn init_dtm(b bool, m int) !&DynamicTemplateManager {
+ temp_folder := os.join_path(dtm.vtmp_dir, dtm.temp_dtm_dir)
+ vcache_path := os.join_path(temp_folder, dtm.temp_cache_dir)
+ templates_path := os.join_path(temp_folder, dtm.temp_templates_dir)
+
+ mut dtm := create_dtm()
+
+ init_params := DynamicTemplateManagerInitialisationParams{
+ active_cache_server: b
+ max_size_data_in_mem: m
+ test_cache_dir: vcache_path
+ test_template_dir: templates_path
+ }
+
+ initialize_dtm(mut dtm, init_params)!
+
+ return dtm
+}
+
+fn (mut tm DynamicTemplateManager) create_cache() string {
+ temp_html_file := os.join_path(tm.template_folder, dtm.temp_html_fp)
+ html_last_mod := os.file_last_mod_unix(temp_html_file)
+ c_time := get_current_unix_micro_timestamp()
+ cache_delay_exp := i64(500) * i64(1000000)
+ placeholder := map[string]DtmMultiTypeMap{}
+ content_checksum := ''
+ html := tm.create_template_cache_and_display_html(.new, html_last_mod, c_time, temp_html_file,
+ dtm.temp_html_n, cache_delay_exp, &placeholder, content_checksum)
+ time.sleep(5 * time.millisecond)
+ return html
+}
diff --git a/vlib/x/templating/dtm/escape_html_strings_in_templates.v b/vlib/x/templating/dtm/escape_html_strings_in_templates.v
new file mode 100644
index 0000000000..fbe88a3640
--- /dev/null
+++ b/vlib/x/templating/dtm/escape_html_strings_in_templates.v
@@ -0,0 +1,7 @@
+module dtm
+
+import encoding.html
+
+fn filter(s string) string {
+ return html.escape(s)
+}