Added feed aggregation module.

Redesigned the CMS_BLOCK system,
   - added condition attribute. It can be set via configuration file
     with
     [blocks]
      {blockid}.region={region_name}
      {blockid}.conditions[]=is_front
      {blockid}.conditions[]=path:location-path/foo/bar
   - For backward compatibility, the CMS will check only conditions for block name prefixed by "?".
Improved the configuration library to support list and table properties.
Updated theme for now, to include the feed examples.
Added "cache" classes, to ease caching of html output for instance. (TODO: improve by providing a cache manager).
This commit is contained in:
2015-10-08 13:56:31 +02:00
parent abebd00a4f
commit 463105f29f
32 changed files with 1504 additions and 108 deletions

94
src/cache/cms_cache.e vendored Normal file
View File

@@ -0,0 +1,94 @@
note
description: "Abstract interface for cache of value conforming to formal {G}."
date: "$Date: 2014-12-03 16:12:08 +0100 (mer., 03 déc. 2014) $"
revision: "$Revision: 96232 $"
deferred class
CMS_CACHE [G -> ANY]
feature -- Status report
exists: BOOLEAN
-- Do associated cache file exists?
deferred
end
expired (a_reference_date: detachable DATE_TIME; a_duration_in_seconds: INTEGER): BOOLEAN
-- Is associated cached item expired?
-- If `a_reference_date' is attached, cache is expired if `a_reference' is more recent than cached item.
local
d1, d2: DATE_TIME
do
if exists then
if
a_reference_date /= Void and then
a_reference_date > cache_date_time
then
Result := True
else
if a_duration_in_seconds = -1 then
Result := False -- Never expires
elseif a_duration_in_seconds = 0 then
Result := True -- Always expires
elseif a_duration_in_seconds > 0 then
d1 := cache_date_time
d2 := current_date_time
d2.second_add (- a_duration_in_seconds) --| do not modify `cache_date_time'
Result := d2 > d1 -- cached date + duration is older than current date
else
-- Invalid expiration value
-- thus always expired.
Result := True
end
end
else
Result := True
end
end
feature -- Access
item: detachable G
-- Value from the cache.
deferred
end
cache_date_time: DATE_TIME
-- Date time for current cache if exists.
-- Note: it may be UTC or not , depending on cache type.
deferred
end
cache_duration_in_seconds: INTEGER_64
-- Number of seconds since cache was set.
require
exists: exists
local
d1, d2: DATE_TIME
do
d1 := cache_date_time
d2 := current_date_time
Result := d2.relative_duration (d1).seconds_count
end
current_date_time: DATE_TIME
-- Current date time for relative duration with cache_date_time.
deferred
end
feature -- Element change
delete
-- Remove cache.
deferred
end
put (g: G)
-- Put `g' into cache.
deferred
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

150
src/cache/cms_file_cache.e vendored Normal file
View File

@@ -0,0 +1,150 @@
note
description: "Cache using a local file."
date: "$Date: 2015-09-24 18:24:06 +0200 (jeu., 24 sept. 2015) $"
revision: "$Revision: 97926 $"
deferred class
CMS_FILE_CACHE [G -> ANY]
inherit
CMS_CACHE [G]
feature {NONE} -- Initialization
make (a_cache_filename: PATH)
do
path := a_cache_filename
end
path: PATH
feature -- Status report
exists: BOOLEAN
-- Do associated cache file exists?
local
ut: FILE_UTILITIES
do
Result := ut.file_path_exists (path)
end
feature -- Access
cache_date_time: DATE_TIME
-- <Precursor>
local
f: RAW_FILE
do
create f.make_with_path (path)
if f.exists then
Result := utc_file_date_time (f)
else
create Result.make_now_utc
end
end
current_date_time: DATE_TIME
-- <Precursor>
do
-- UTC, since `cache_date_time' is UTC!
create Result.make_now_utc
end
file_size: INTEGER
-- Associated file size.
require
exists: exists
local
f: RAW_FILE
do
create f.make_with_path (path)
if f.exists and then f.is_access_readable then
Result := f.count
end
end
item: detachable G
local
f: RAW_FILE
retried: BOOLEAN
do
if not retried then
create f.make_with_path (path)
if f.exists and then f.is_access_readable then
f.open_read
Result := file_to_item (f)
f.close
end
end
rescue
retried := True
retry
end
feature -- Element change
delete
-- <Precursor>
local
f: RAW_FILE
retried: BOOLEAN
do
if not retried then
create f.make_with_path (path)
-- Create recursively parent directory if it does not exists.
if f.exists and then f.is_access_writable then
f.delete
end
end
rescue
retried := True
retry
end
put (g: G)
-- <Precursor>
local
f: RAW_FILE
d: DIRECTORY
do
create f.make_with_path (path)
-- Create recursively parent directory if it does not exists.
create d.make_with_path (path.parent)
if not d.exists then
d.recursive_create_dir
end
if not f.exists or else f.is_access_writable then
f.open_write
item_to_file (g, f)
f.close
end
end
feature -- Helpers
utc_file_date_time (f: FILE): DATE_TIME
-- Last change date for file `f'.
require
f.exists
do
create Result.make_from_epoch (f.date.as_integer_32)
end
feature {NONE} -- Implementation
file_to_item (f: FILE): detachable G
require
is_open_write: f.is_open_read
deferred
end
item_to_file (g: G; f: FILE)
require
is_open_write: f.is_open_write
deferred
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

49
src/cache/cms_file_object_cache.e vendored Normal file
View File

@@ -0,0 +1,49 @@
note
description: "Cache for value conforming to formal {G}, and implemented using local file."
date: "$Date: 2014-10-30 12:13:25 +0100 (jeu., 30 oct. 2014) $"
revision: "$Revision: 96016 $"
class
CMS_FILE_OBJECT_CACHE [G -> ANY]
inherit
CMS_FILE_CACHE [G]
SED_STORABLE_FACILITIES
create
make
feature {NONE} -- Implementation
file_to_item (f: FILE): detachable G
local
retried: BOOLEAN
l_reader: SED_MEDIUM_READER_WRITER
l_void: detachable G
do
if retried then
Result := l_void
else
create l_reader.make_for_reading (f)
if attached {G} retrieved (l_reader, True) as l_data then
Result := l_data
end
end
rescue
retried := True
retry
end
item_to_file (a_data: G; f: FILE)
local
l_writer: SED_MEDIUM_READER_WRITER
do
create l_writer.make_for_writing (f)
basic_store (a_data, l_writer, True)
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

69
src/cache/cms_file_string_8_cache.e vendored Normal file
View File

@@ -0,0 +1,69 @@
note
description: "Cache system for STRING_8 value."
date: "$Date: 2014-10-30 12:13:25 +0100 (jeu., 30 oct. 2014) $"
revision: "$Revision: 96016 $"
class
CMS_FILE_STRING_8_CACHE
inherit
CMS_FILE_CACHE [STRING]
create
make
feature -- Access
append_to (a_output: STRING)
-- Append `item' to `a_output'.
local
f: RAW_FILE
retried: BOOLEAN
do
if not retried then
create f.make_with_path (path)
if f.exists and then f.is_access_readable then
f.open_read
if attached file_to_item (f) as s then
a_output.append (s)
end
f.close
end
end
rescue
retried := True
retry
end
feature {NONE} -- Implementation
file_to_item (f: FILE): detachable STRING
local
retried: BOOLEAN
do
if retried then
Result := Void
else
from
create Result.make_empty
until
f.exhausted or f.end_of_file
loop
f.read_stream_thread_aware (1_024)
Result.append (f.last_string)
end
end
rescue
retried := True
retry
end
item_to_file (a_data: STRING; f: FILE)
do
f.put_string (a_data)
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

61
src/cache/cms_memory_cache.e vendored Normal file
View File

@@ -0,0 +1,61 @@
note
description: "Cache relying on memory."
date: "$Date: 2014-12-03 16:57:00 +0100 (mer., 03 déc. 2014) $"
revision: "$Revision: 96234 $"
deferred class
CMS_MEMORY_CACHE [G -> ANY]
inherit
CMS_CACHE [G]
feature {NONE} -- Initialization
make
do
cache_date_time := current_date_time
end
feature -- Status report
exists: BOOLEAN
-- Do associated cache memory exists?
do
Result := item /= Void
end
feature -- Access
cache_date_time: DATE_TIME
current_date_time: DATE_TIME
-- <Precursor>
do
create Result.make_now_utc
end
item: detachable G
feature -- Element change
delete
-- <Precursor>
local
l_default: detachable G
do
item := l_default
cache_date_time := current_date_time
end
put (g: G)
-- <Precursor>
do
item := g
cache_date_time := current_date_time
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -68,6 +68,18 @@ feature -- Access
Result := configuration.resolved_text_item (a_name)
end
text_list_item (a_name: READABLE_STRING_GENERAL): detachable LIST [READABLE_STRING_32]
-- Configuration values associated with `a_name', if any.
do
Result := configuration.text_list_item (a_name)
end
text_table_item (a_name: READABLE_STRING_GENERAL): detachable STRING_TABLE [READABLE_STRING_32]
-- Configuration indexed values associated with `a_name', if any.
do
Result := configuration.text_table_item (a_name)
end
string_8_item (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_8
-- String 8 configuration value associated with `a_name', if any.
local

View File

@@ -196,6 +196,16 @@ feature -- Query
deferred
end
text_list_item (a_name: READABLE_STRING_GENERAL): detachable LIST [READABLE_STRING_32]
-- Configuration values associated with `a_name', if any.
deferred
end
text_table_item (a_name: READABLE_STRING_GENERAL): detachable STRING_TABLE [READABLE_STRING_32]
-- Configuration indexed values associated with `a_name', if any.
deferred
end
text_item_or_default (a_name: READABLE_STRING_GENERAL; a_default_value: READABLE_STRING_GENERAL): READABLE_STRING_32
-- `text_item' associated with `a_name' or if none, `a_default_value'.
do

View File

@@ -15,6 +15,8 @@ feature -- Hook
block_list: ITERABLE [like {CMS_BLOCK}.name]
-- List of block names, managed by current object.
-- If prefixed by "?", condition will be check
-- to determine if it should be displayed (and computed) or not.
deferred
end
@@ -23,4 +25,7 @@ feature -- Hook
deferred
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -138,6 +138,9 @@ feature -- Hook: block
invoke_block (a_response: CMS_RESPONSE)
-- Invoke block hook for response `a_response' in order to get block from modules.
local
bl: READABLE_STRING_8
bl_optional: BOOLEAN
do
if attached subscribers ({CMS_HOOK_BLOCK}) as lst then
across
@@ -147,7 +150,16 @@ feature -- Hook: block
across
h.block_list as blst
loop
h.get_block_view (blst.item, a_response)
bl := blst.item
bl_optional := bl.count > 0 and bl[1] = '?'
if bl_optional then
bl := bl.substring (2, bl.count)
if a_response.is_block_included (bl, False) then
h.get_block_view (bl, a_response)
end
else
h.get_block_view (bl, a_response)
end
end
end
end

View File

@@ -6,11 +6,12 @@ note
class
CMS_BLOCK_EXPRESSION_CONDITION
inherit
inherit
CMS_BLOCK_CONDITION
create
make
make,
make_none
feature {NONE} -- Initialization
@@ -19,9 +20,14 @@ feature {NONE} -- Initialization
expression := a_exp
end
make_none
do
make ("<none>")
end
feature -- Access
description: READABLE_STRING_32
description: STRING_32
do
create Result.make_from_string_general ("Expression: %"")
Result.append_string_general (expression)
@@ -33,10 +39,18 @@ feature -- Access
feature -- Evaluation
satisfied_for_response (res: CMS_RESPONSE): BOOLEAN
local
exp: like expression
do
if expression.same_string ("is_front") then
exp := expression
if exp.same_string ("is_front") then
Result := res.is_front
elseif expression.starts_with ("path=") then
elseif exp.same_string ("*") then
Result := True
elseif exp.same_string ("<none>") then
Result := False
elseif exp.starts_with ("path:") then
Result := res.location.same_string (exp.substring (6, exp.count))
end
end

View File

@@ -0,0 +1,45 @@
note
description: "Condition for block to be displayed based on location."
date: "$Date$"
revision: "$Revision$"
class
CMS_BLOCK_LOCATION_CONDITION
inherit
CMS_BLOCK_CONDITION
create
make_with_location
feature {NONE} -- Initialization
make_with_location (a_location: READABLE_STRING_8)
do
location := a_location
end
feature -- Access
description: STRING_32
do
create Result.make_from_string_general ("Location: %"")
Result.append_string_general (location)
Result.append_character ('%"')
end
location: STRING
feature -- Evaluation
satisfied_for_response (res: CMS_RESPONSE): BOOLEAN
local
loc: like location
do
Result := res.location.same_string (loc)
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -70,6 +70,19 @@ feature -- Element change
opts.remove_css_class (a_class)
end
add_condition (a_condition: CMS_BLOCK_CONDITION)
-- Add condition `a_condition'.
local
l_conditions: like conditions
do
l_conditions := conditions
if l_conditions = Void then
create {ARRAYED_LIST [CMS_BLOCK_CONDITION]} l_conditions.make (1)
conditions := l_conditions
end
l_conditions.force (a_condition)
end
feature -- Conversion
to_html (a_theme: CMS_THEME): STRING_8

View File

@@ -58,7 +58,7 @@ feature -- Hooks
block_list: ITERABLE [like {CMS_BLOCK}.name]
do
Result := <<"debug-info">>
Result := <<"?debug-info">>
end
get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
@@ -67,14 +67,13 @@ feature -- Hooks
dbg: WSF_DEBUG_INFORMATION
s: STRING
do
if a_response.theme.has_region ("debug") then
create dbg.make
create s.make_empty
dbg.append_information_to (a_response.request, a_response.response, s)
append_info_to ("Storage", a_response.api.storage.generator, a_response, s)
create b.make ("debug-info", "Debug", s, a_response.formats.plain_text)
a_response.add_block (b, "footer")
end
create dbg.make
create s.make_empty
dbg.append_information_to (a_response.request, a_response.response, s)
append_info_to ("Storage", a_response.api.storage.generator, a_response, s)
create b.make ("debug-info", "Debug", s, a_response.formats.plain_text)
b.add_condition (create {CMS_BLOCK_EXPRESSION_CONDITION}.make_none)
a_response.add_block (b, "footer")
end
feature -- Handler

View File

@@ -423,6 +423,36 @@ feature -- Blocks initialization
Result := setup.text_item_or_default ("blocks." + a_block_id + ".region", a_default_region)
end
block_conditions (a_block_id: READABLE_STRING_8): detachable ARRAYED_LIST [CMS_BLOCK_EXPRESSION_CONDITION]
-- Condition associated with `a_block_id' in configuration, if any.
do
if attached setup.text_item ("blocks." + a_block_id + ".condition") as s then
create Result.make (1)
Result.force (create {CMS_BLOCK_EXPRESSION_CONDITION}.make (s))
end
if attached setup.text_list_item ("blocks." + a_block_id + ".conditions") as lst then
if Result = Void then
create Result.make (lst.count)
across
lst as ic
loop
Result.force (create {CMS_BLOCK_EXPRESSION_CONDITION}.make (ic.item))
end
end
end
end
is_block_included (a_block_id: READABLE_STRING_8; dft: BOOLEAN): BOOLEAN
-- Is block `a_block_id' included in current response?
-- If no preference, return `dft'.
do
if attached block_conditions (a_block_id) as l_conditions then
Result := across l_conditions as ic some ic.item.satisfied_for_response (Current) end
else
Result := dft
end
end
feature -- Blocks regions
regions: STRING_TABLE [CMS_BLOCK_REGION]
@@ -457,10 +487,25 @@ feature -- Blocks regions
end
end
feature -- Blocks
feature -- Blocks
put_block (b: CMS_BLOCK; a_default_region: detachable READABLE_STRING_8; is_block_included_by_default: BOOLEAN)
-- Add block `b' to associated region or `a_default_region' if provided
-- and check optional associated condition.
-- If no condition then use `is_block_included_by_default' to
-- decide if block is included or not.
local
l_region: detachable like block_region
do
if is_block_included (b.name, is_block_included_by_default) then
l_region := block_region (b, a_default_region)
l_region.extend (b)
end
end
add_block (b: CMS_BLOCK; a_default_region: detachable READABLE_STRING_8)
-- Add block `b' to associated region or `a_default_region' if provided.
-- WARNING: ignore any block condition! USE WITH CARE!
local
l_region: detachable like block_region
do
@@ -473,29 +518,29 @@ feature -- Blocks
debug ("refactor_fixme")
fixme ("find a way to have this in configuration or database, and allow different order")
end
add_block (top_header_block, "top")
add_block (header_block, "header")
put_block (top_header_block, "top", True)
put_block (header_block, "header", True)
if attached message_block as m then
add_block (m, "content")
put_block (m, "content", True)
end
if attached primary_tabs_block as m then
add_block (m, "content")
put_block (m, "content", True)
end
add_block (content_block, "content")
add_block (content_block, "content") -- Can not be disabled!
if attached management_menu_block as l_block then
add_block (l_block, "sidebar_first")
put_block (l_block, "sidebar_first", True)
end
if attached navigation_menu_block as l_block then
add_block (l_block, "sidebar_first")
put_block (l_block, "sidebar_first", True)
end
if attached user_menu_block as l_block then
add_block (l_block, "sidebar_second")
put_block (l_block, "sidebar_second", True)
end
hooks.invoke_block (Current)
debug ("cms")
add_block (create {CMS_CONTENT_BLOCK}.make ("made_with", Void, "Made with <a href=%"http://www.eiffel.com/%">EWF</a>", Void), "footer")
put_block (create {CMS_CONTENT_BLOCK}.make ("made_with", Void, "Made with <a href=%"https://www.eiffel.org/%">EWF</a>", Void), "footer", True)
end
end