aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormistic100 <mistic@piwigo.org>2014-05-24 14:18:04 +0000
committermistic100 <mistic@piwigo.org>2014-05-24 14:18:04 +0000
commitfea2a4efd1ad085def7cf84cc444325d0815b62e (patch)
tree892dcb971d34efd7cbff6c31375448a09dfbe73b
parent59f418f7986594895a2352e8895250069cff336a (diff)
feature 3077 : improve cache invalidation
- add "lastmodified" automatic field for categories, groups, users, tags and images tables - provide a "server key" to the client cache manager git-svn-id: http://piwigo.org/svn/trunk@28532 68402e56-0260-453c-a942-63ccdbb3a9ee
-rw-r--r--admin/batch_manager_global.php11
-rw-r--r--admin/batch_manager_unit.php5
-rw-r--r--admin/cat_perm.php6
-rw-r--r--admin/include/functions.php61
-rw-r--r--admin/picture_modify.php6
-rw-r--r--admin/themes/default/js/LocalStorageCache.js22
-rw-r--r--admin/themes/default/template/batch_manager_global.tpl35
-rw-r--r--admin/themes/default/template/batch_manager_unit.tpl35
-rw-r--r--admin/themes/default/template/cat_perm.tpl84
-rw-r--r--admin/themes/default/template/picture_modify.tpl49
-rw-r--r--install/db/141-database.php45
-rw-r--r--install/piwigo_structure-mysql.sql20
12 files changed, 263 insertions, 116 deletions
diff --git a/admin/batch_manager_global.php b/admin/batch_manager_global.php
index 57cb2acdf..4bec501db 100644
--- a/admin/batch_manager_global.php
+++ b/admin/batch_manager_global.php
@@ -701,12 +701,11 @@ SELECT id,path,representative_ext,file,filesize,level,name,width,height,rotation
$template->assign('thumb_params', $thumb_params);
}
-$template->assign(
- array(
- 'nb_thumbs_page' => $nb_thumbs_page,
- 'nb_thumbs_set' => count($page['cat_elements_id']),
- )
- );
+$template->assign(array(
+ 'nb_thumbs_page' => $nb_thumbs_page,
+ 'nb_thumbs_set' => count($page['cat_elements_id']),
+ 'CACHE_KEYS' => get_admin_client_cache_keys(array('tags'))
+ ));
trigger_action('loc_end_element_set_global');
diff --git a/admin/batch_manager_unit.php b/admin/batch_manager_unit.php
index bbbe4810c..05424dccf 100644
--- a/admin/batch_manager_unit.php
+++ b/admin/batch_manager_unit.php
@@ -248,7 +248,10 @@ SELECT
));
}
- $template->assign('ELEMENT_IDS', implode(',', $element_ids));
+ $template->assign(array(
+ 'ELEMENT_IDS' => implode(',', $element_ids),
+ 'CACHE_KEYS' => get_admin_client_cache_keys(array('tags')),
+ ));
}
trigger_action('loc_end_element_set_unit');
diff --git a/admin/cat_perm.php b/admin/cat_perm.php
index 71653d9ec..c42b3eaa2 100644
--- a/admin/cat_perm.php
+++ b/admin/cat_perm.php
@@ -298,7 +298,11 @@ SELECT user_id, group_id
// +-----------------------------------------------------------------------+
// | sending html code |
// +-----------------------------------------------------------------------+
-$template->assign(array('PWG_TOKEN' => get_pwg_token(), 'INHERIT' => $conf['inheritance_by_default']));
+$template->assign(array(
+ 'PWG_TOKEN' => get_pwg_token(),
+ 'INHERIT' => $conf['inheritance_by_default'],
+ 'CACHE_KEYS' => get_admin_client_cache_keys(array('groups', 'users')),
+ ));
$template->assign_var_from_handle('ADMIN_CONTENT', 'cat_perm');
?>
diff --git a/admin/include/functions.php b/admin/include/functions.php
index 384189408..a3778f595 100644
--- a/admin/include/functions.php
+++ b/admin/include/functions.php
@@ -400,13 +400,8 @@ function delete_orphan_tags()
{
$orphan_tag_ids[] = $tag['id'];
}
-
- $query = '
-DELETE
- FROM '.TAGS_TABLE.'
- WHERE id IN ('.implode(',', $orphan_tag_ids).')
-;';
- pwg_query($query);
+
+ delete_tags($orphan_tag_ids);
}
}
@@ -2733,4 +2728,54 @@ function deltree($path, $trash_path=null)
}
}
-?> \ No newline at end of file
+/**
+ * Returns keys to identify the state of main tables. A key consists of the
+ * last modification timestamp and the total of items (separated by a _).
+ * Additionally returns the hash of root path.
+ * Used to invalidate LocalStorage cache on admin pages.
+ *
+ * @param string|string[] list of keys to retrieve (categories,groups,images,tags,users)
+ * @return string[]
+ */
+function get_admin_client_cache_keys($requested=array())
+{
+ $tables = array(
+ 'categories' => CATEGORIES_TABLE,
+ 'groups' => GROUPS_TABLE,
+ 'images' => IMAGES_TABLE,
+ 'tags' => TAGS_TABLE,
+ 'users' => USER_INFOS_TABLE
+ );
+
+ if (!is_array($requested))
+ {
+ $requested = array($requested);
+ }
+ if (empty($requested))
+ {
+ $requested = array_keys($tables);
+ }
+ else
+ {
+ $requested = array_intersect($requested, array_keys($tables));
+ }
+
+ $keys = array(
+ '_hash' => md5(get_absolute_root_url()),
+ );
+
+ foreach ($requested as $item)
+ {
+ $query = '
+SELECT CONCAT(
+ UNIX_TIMESTAMP(MAX(lastmodified)),
+ "_",
+ COUNT(*)
+ )
+ FROM '. $tables[$item] .'
+;';
+ list($keys[$item]) = pwg_db_fetch_row(pwg_query($query));
+ }
+
+ return $keys;
+} \ No newline at end of file
diff --git a/admin/picture_modify.php b/admin/picture_modify.php
index 1b99e5f55..370d64caf 100644
--- a/admin/picture_modify.php
+++ b/admin/picture_modify.php
@@ -408,7 +408,11 @@ SELECT id
;';
$associate_options_selected = query2array($query, null, 'id');
-$template->assign(compact('associate_options_selected', 'represent_options_selected'));
+$template->assign(array(
+ 'associate_options_selected' => $associate_options_selected,
+ 'represent_options_selected' => $represent_options_selected,
+ 'CACHE_KEYS' => get_admin_client_cache_keys(array('tags', 'categories')),
+ ));
trigger_action('loc_end_picture_modify');
diff --git a/admin/themes/default/js/LocalStorageCache.js b/admin/themes/default/js/LocalStorageCache.js
index 49a4fa98d..c18171efc 100644
--- a/admin/themes/default/js/LocalStorageCache.js
+++ b/admin/themes/default/js/LocalStorageCache.js
@@ -1,7 +1,8 @@
-var LocalStorageCache = function(key, lifetime, loader) {
- this.key = key;
- this.lifetime = lifetime*1000;
- this.loader = loader;
+var LocalStorageCache = function(options) {
+ this.key = options.key + '-' + options.serverId;
+ this.serverKey = options.serverKey;
+ this.lifetime = options.lifetime ? options.lifetime*1000 : 3600*1000;
+ this.loader = options.loader;
this.storage = window.localStorage;
this.ready = !!this.storage;
@@ -14,28 +15,23 @@ LocalStorageCache.prototype.get = function(callback) {
if (this.ready && this.storage[this.key] != undefined) {
var cache = JSON.parse(this.storage[this.key]);
- if (now - cache.timestamp <= this.lifetime) {
+ if (now - cache.timestamp <= this.lifetime && cache.key == this.serverKey) {
callback(cache.data);
return;
}
}
this.loader(function(data) {
- if (that.ready) {
- that.storage[that.key] = JSON.stringify({
- timestamp: now,
- data: data
- });
- }
-
+ that.set.call(that, data);
callback(data);
});
};
LocalStorageCache.prototype.set = function(data) {
if (this.ready) {
- that.storage[that.key] = JSON.stringify({
+ this.storage[this.key] = JSON.stringify({
timestamp: new Date().getTime(),
+ key: this.serverKey,
data: data
});
}
diff --git a/admin/themes/default/template/batch_manager_global.tpl b/admin/themes/default/template/batch_manager_global.tpl
index 3e22edb02..087f9916a 100644
--- a/admin/themes/default/template/batch_manager_global.tpl
+++ b/admin/themes/default/template/batch_manager_global.tpl
@@ -68,16 +68,22 @@ jQuery(document).ready(function() {ldelim}
jQuery("a.preview-box").colorbox();
{* <!-- TAGS --> *}
- var tagsCache = new LocalStorageCache('tagsAdminList', 5*60, function(callback) {
- jQuery.getJSON('{$ROOT_URL}ws.php?format=json&method=pwg.tags.getAdminList', function(data) {
- var tags = data.result.tags;
-
- for (var i=0, l=tags.length; i<l; i++) {
- tags[i].id = '~~' + tags[i].id + '~~';
- }
-
- callback(tags);
- });
+ var tagsCache = new LocalStorageCache({
+ key: 'tagsAdminList',
+ serverKey: '{$CACHE_KEYS.tags}',
+ serverId: '{$CACHE_KEYS._hash}',
+
+ loader: function(callback) {
+ jQuery.getJSON('{$ROOT_URL}ws.php?format=json&method=pwg.tags.getAdminList', function(data) {
+ var tags = data.result.tags;
+
+ for (var i=0, l=tags.length; i<l; i++) {
+ tags[i].id = '~~' + tags[i].id + '~~';
+ }
+
+ callback(tags);
+ });
+ }
});
jQuery('[data-selectize=tags]').selectize({
@@ -92,14 +98,7 @@ jQuery(document).ready(function() {ldelim}
labelField: 'name',
searchField: ['name'],
plugins: ['remove_button'],
- create: function(input, callback) {
- tagsCache.clear();
-
- callback({
- id: input,
- name: input
- });
- }
+ create: true
});
tagsCache.get(function(tags) {
diff --git a/admin/themes/default/template/batch_manager_unit.tpl b/admin/themes/default/template/batch_manager_unit.tpl
index d0d06e11d..6d5a90ddd 100644
--- a/admin/themes/default/template/batch_manager_unit.tpl
+++ b/admin/themes/default/template/batch_manager_unit.tpl
@@ -10,16 +10,22 @@
{footer_script}
(function(){
{* <!-- TAGS --> *}
-var tagsCache = new LocalStorageCache('tagsAdminList', 5*60, function(callback) {
- jQuery.getJSON('{$ROOT_URL}ws.php?format=json&method=pwg.tags.getAdminList', function(data) {
- var tags = data.result.tags;
-
- for (var i=0, l=tags.length; i<l; i++) {
- tags[i].id = '~~' + tags[i].id + '~~';
- }
-
- callback(tags);
- });
+var tagsCache = new LocalStorageCache({
+ key: 'tagsAdminList',
+ serverKey: '{$CACHE_KEYS.tags}',
+ serverId: '{$CACHE_KEYS._hash}',
+
+ loader: function(callback) {
+ jQuery.getJSON('{$ROOT_URL}ws.php?format=json&method=pwg.tags.getAdminList', function(data) {
+ var tags = data.result.tags;
+
+ for (var i=0, l=tags.length; i<l; i++) {
+ tags[i].id = '~~' + tags[i].id + '~~';
+ }
+
+ callback(tags);
+ });
+ }
});
jQuery('[data-selectize=tags]').selectize({
@@ -27,14 +33,7 @@ jQuery('[data-selectize=tags]').selectize({
labelField: 'name',
searchField: ['name'],
plugins: ['remove_button'],
- create: function(input, callback) {
- tagsCache.clear();
-
- callback({
- id: input,
- name: input
- });
- }
+ create: true
});
tagsCache.get(function(tags) {
diff --git a/admin/themes/default/template/cat_perm.tpl b/admin/themes/default/template/cat_perm.tpl
index ba0813014..d810475b2 100644
--- a/admin/themes/default/template/cat_perm.tpl
+++ b/admin/themes/default/template/cat_perm.tpl
@@ -6,10 +6,16 @@
{footer_script}
(function(){
{* <!-- GROUPS --> *}
-var groupsCache = new LocalStorageCache('groupsAdminList', 5*60, function(callback) {
- jQuery.getJSON('{$ROOT_URL}ws.php?format=json&method=pwg.groups.getList&per_page=99999', function(data) {
- callback(data.result.groups);
- });
+var groupsCache = new LocalStorageCache({
+ key: 'groupsAdminList',
+ serverKey: '{$CACHE_KEYS.groups}',
+ serverId: '{$CACHE_KEYS._hash}',
+
+ loader: function(callback) {
+ jQuery.getJSON('{$ROOT_URL}ws.php?format=json&method=pwg.groups.getList&per_page=99999', function(data) {
+ callback(data.result.groups);
+ });
+ }
});
jQuery('[data-selectize=groups]').selectize({
@@ -32,22 +38,28 @@ groupsCache.get(function(groups) {
});
{* <!-- USERS --> *}
-var usersCache = new LocalStorageCache('usersAdminList', 5*60, function(callback) {
- var page = 0,
- users = [];
-
- (function load(page){
- jQuery.getJSON('{$ROOT_URL}ws.php?format=json&method=pwg.users.getList&display=username&per_page=99999&page='+ page, function(data) {
- users = users.concat(data.result.users);
-
- if (data.result.paging.count == data.result.paging.per_page) {
- load(++page);
- }
- else {
- callback(users);
- }
- });
- }(page));
+var usersCache = new LocalStorageCache({
+ key: 'usersAdminList',
+ serverKey: '{$CACHE_KEYS.users}',
+ serverId: '{$CACHE_KEYS._hash}',
+
+ loader: function(callback) {
+ var users = [];
+
+ // recursive loader
+ (function load(page){
+ jQuery.getJSON('{$ROOT_URL}ws.php?format=json&method=pwg.users.getList&display=username&per_page=99999&page='+ page, function(data) {
+ users = users.concat(data.result.users);
+
+ if (data.result.paging.count == data.result.paging.per_page) {
+ load(++page);
+ }
+ else {
+ callback(users);
+ }
+ });
+ }(0));
+ }
});
jQuery('[data-selectize=users]').selectize({
@@ -68,6 +80,29 @@ usersCache.get(function(users) {
}, this));
});
});
+
+{* <!-- TOGGLES --> *}
+function checkStatusOptions() {
+ if (jQuery("input[name=status]:checked").val() == "private") {
+ jQuery("#privateOptions, #applytoSubAction").show();
+ }
+ else {
+ jQuery("#privateOptions, #applytoSubAction").hide();
+ }
+}
+
+checkStatusOptions();
+jQuery("#selectStatus").change(function() {
+ checkStatusOptions();
+});
+
+{if isset($nb_users_granted_indirect) && $nb_users_granted_indirect>0}
+ jQuery(".toggle-indirectPermissions").click(function(e){
+ jQuery(".toggle-indirectPermissions").toggle();
+ jQuery("#indirectPermissionsDetails").toggle();
+ e.preventDefault();
+ });
+{/if}
}());
{/footer_script}
@@ -111,8 +146,8 @@ usersCache.get(function(users) {
{if isset($nb_users_granted_indirect) && $nb_users_granted_indirect>0}
<p>
{'%u users have automatic permission because they belong to a granted group.'|@translate:$nb_users_granted_indirect}
- <a href="#" id="indirectPermissionsDetailsHide" style="display:none">{'hide details'|@translate}</a>
- <a href="#" id="indirectPermissionsDetailsShow">{'show details'|@translate}</a>
+ <a href="#" class="toggle-indirectPermissions" style="display:none">{'hide details'|@translate}</a>
+ <a href="#" class="toggle-indirectPermissions">{'show details'|@translate}</a>
<ul id="indirectPermissionsDetails" style="display:none">
{foreach from=$user_granted_indirect_groups item=group_details}
@@ -184,7 +219,10 @@ usersCache.get(function(users) {
<p style="margin:12px;text-align:left;">
<input class="submit" type="submit" value="{'Save Settings'|@translate}" name="submit">
- <label id="applytoSubAction" style="display:none;"><input type="checkbox" name="apply_on_sub" {if $INHERIT}checked="checked"{/if}>{'Apply to sub-albums'|@translate}</label>
+ <label id="applytoSubAction" style="display:none;">
+ <input type="checkbox" name="apply_on_sub" {if $INHERIT}checked="checked"{/if}>
+ {'Apply to sub-albums'|@translate}
+ </label>
</p>
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN}">
diff --git a/admin/themes/default/template/picture_modify.tpl b/admin/themes/default/template/picture_modify.tpl
index f48a152d6..602b9b126 100644
--- a/admin/themes/default/template/picture_modify.tpl
+++ b/admin/themes/default/template/picture_modify.tpl
@@ -10,10 +10,16 @@
{footer_script}
(function(){
{* <!-- CATEGORIES --> *}
-var categoriesCache = new LocalStorageCache('categoriesAdminList', 5*60, function(callback) {
- jQuery.getJSON('{$ROOT_URL}ws.php?format=json&method=pwg.categories.getAdminList', function(data) {
- callback(data.result.categories);
- });
+var categoriesCache = new LocalStorageCache({
+ key: 'categoriesAdminList',
+ serverKey: '{$CACHE_KEYS.categories}',
+ serverId: '{$CACHE_KEYS._hash}',
+
+ loader: function(callback) {
+ jQuery.getJSON('{$ROOT_URL}ws.php?format=json&method=pwg.categories.getAdminList', function(data) {
+ callback(data.result.categories);
+ });
+ }
});
jQuery('[data-selectize=categories]').selectize({
@@ -36,16 +42,22 @@ categoriesCache.get(function(categories) {
});
{* <!-- TAGS --> *}
-var tagsCache = new LocalStorageCache('tagsAdminList', 5*60, function(callback) {
- jQuery.getJSON('{$ROOT_URL}ws.php?format=json&method=pwg.tags.getAdminList', function(data) {
- var tags = data.result.tags;
-
- for (var i=0, l=tags.length; i<l; i++) {
- tags[i].id = '~~' + tags[i].id + '~~';
- }
-
- callback(tags);
- });
+var tagsCache = new LocalStorageCache({
+ key: 'tagsAdminList',
+ serverKey: '{$CACHE_KEYS.tags}',
+ serverId: '{$CACHE_KEYS._hash}',
+
+ loader: function(callback) {
+ jQuery.getJSON('{$ROOT_URL}ws.php?format=json&method=pwg.tags.getAdminList', function(data) {
+ var tags = data.result.tags;
+
+ for (var i=0, l=tags.length; i<l; i++) {
+ tags[i].id = '~~' + tags[i].id + '~~';
+ }
+
+ callback(tags);
+ });
+ }
});
jQuery('[data-selectize=tags]').selectize({
@@ -53,14 +65,7 @@ jQuery('[data-selectize=tags]').selectize({
labelField: 'name',
searchField: ['name'],
plugins: ['remove_button'],
- create: function(input, callback) {
- tagsCache.clear();
-
- callback({
- id: input,
- name: input
- });
- }
+ create: true
});
tagsCache.get(function(tags) {
diff --git a/install/db/141-database.php b/install/db/141-database.php
new file mode 100644
index 000000000..f6a157123
--- /dev/null
+++ b/install/db/141-database.php
@@ -0,0 +1,45 @@
+<?php
+// +-----------------------------------------------------------------------+
+// | Piwigo - a PHP based photo gallery |
+// +-----------------------------------------------------------------------+
+// | Copyright(C) 2008-2014 Piwigo Team http://piwigo.org |
+// | Copyright(C) 2003-2008 PhpWebGallery Team http://phpwebgallery.net |
+// | Copyright(C) 2002-2003 Pierrick LE GALL http://le-gall.net/pierrick |
+// +-----------------------------------------------------------------------+
+// | This program is free software; you can redistribute it and/or modify |
+// | it under the terms of the GNU General Public License as published by |
+// | the Free Software Foundation |
+// | |
+// | This program is distributed in the hope that it will be useful, but |
+// | WITHOUT ANY WARRANTY; without even the implied warranty of |
+// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
+// | General Public License for more details. |
+// | |
+// | You should have received a copy of the GNU General Public License |
+// | along with this program; if not, write to the Free Software |
+// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
+// | USA. |
+// +-----------------------------------------------------------------------+
+
+defined('PHPWG_ROOT_PATH') or die('Hacking attempt!');
+
+$upgrade_description = 'add lastmodified field for categories, images, groups, users, tags';
+
+$tables = array(
+ CATEGORIES_TABLE,
+ GROUPS_TABLE,
+ IMAGES_TABLE,
+ TAGS_TABLE,
+ USER_INFOS_TABLE
+ );
+
+foreach ($tables as $table)
+{
+ pwg_query('
+ALTER TABLE '. $table .'
+ ADD `lastmodified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ ADD INDEX `lastmodified` (`lastmodified`)
+;');
+}
+
+echo "\n".$upgrade_description."\n";
diff --git a/install/piwigo_structure-mysql.sql b/install/piwigo_structure-mysql.sql
index 7520d99a4..c05dcb981 100644
--- a/install/piwigo_structure-mysql.sql
+++ b/install/piwigo_structure-mysql.sql
@@ -36,9 +36,11 @@ CREATE TABLE `piwigo_categories` (
`global_rank` varchar(255) default NULL,
`image_order` varchar(128) default NULL,
`permalink` varchar(64) binary default NULL,
+ `lastmodified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `categories_i3` (`permalink`),
- KEY `categories_i2` (`id_uppercat`)
+ KEY `categories_i2` (`id_uppercat`),
+ KEY `lastmodified` (`lastmodified`)
) ENGINE=MyISAM;
--
@@ -106,8 +108,10 @@ CREATE TABLE `piwigo_groups` (
`id` smallint(5) unsigned NOT NULL auto_increment,
`name` varchar(255) NOT NULL default '',
`is_default` enum('true','false') NOT NULL default 'false',
+ `lastmodified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
- UNIQUE KEY `groups_ui1` (`name`)
+ UNIQUE KEY `groups_ui1` (`name`),
+ KEY `lastmodified` (`lastmodified`)
) ENGINE=MyISAM;
--
@@ -199,13 +203,15 @@ CREATE TABLE `piwigo_images` (
`rotation` tinyint unsigned default NULL,
`latitude` double(8, 6) default NULL,
`longitude` double(9, 6) default NULL,
+ `lastmodified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `images_i2` (`date_available`),
KEY `images_i3` (`rating_score`),
KEY `images_i4` (`hit`),
KEY `images_i5` (`date_creation`),
KEY `images_i1` (`storage_category_id`),
- KEY `images_i6` (`latitude`)
+ KEY `images_i6` (`latitude`),
+ KEY `lastmodified` (`lastmodified`)
) ENGINE=MyISAM;
--
@@ -305,8 +311,10 @@ CREATE TABLE `piwigo_tags` (
`id` smallint(5) unsigned NOT NULL auto_increment,
`name` varchar(255) NOT NULL default '',
`url_name` varchar(255) binary NOT NULL default '',
+ `lastmodified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
- KEY `tags_i1` (`url_name`)
+ KEY `tags_i1` (`url_name`),
+ KEY `lastmodified` (`lastmodified`)
) ENGINE=MyISAM;
--
@@ -423,7 +431,9 @@ CREATE TABLE `piwigo_user_infos` (
`enabled_high` enum('true','false') NOT NULL default 'true',
`level` tinyint unsigned NOT NULL default '0',
`activation_key` char(20) default NULL,
- PRIMARY KEY (`user_id`)
+ `lastmodified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`user_id`),
+ KEY `lastmodified` (`lastmodified`)
) ENGINE=MyISAM;
--