From ac0d1a5b479aae0191f81d17a802b1cb6c9346a3 Mon Sep 17 00:00:00 2001 From: plegall Date: Fri, 11 Jun 2021 16:35:29 +0200 Subject: [PATCH] fixes #1419 add photos in a lounge as a temporary space * at the end of the upload of after a maximum duration, move the photos from the lounge to their actual categories. * do not invalidate user cache when photos are added in the lounge, thus avoiding to rebuild cache on every photo uploaded * the lounge system activates itself only beyond 50k (by default) photo --- admin/include/functions.php | 141 +++++++++++++++++- admin/include/functions_upload.inc.php | 32 +++- admin/maintenance.php | 1 + .../default/template/photos_add_direct.tpl | 8 + include/common.inc.php | 2 + include/config_default.inc.php | 11 ++ include/constants.php | 2 + include/functions.inc.php | 44 ++++++ include/functions_category.inc.php | 8 + include/ws_functions/pwg.images.php | 19 ++- install/db/160-database.php | 26 ++++ install/piwigo_structure-mysql.sql | 11 ++ ws.php | 9 ++ 13 files changed, 307 insertions(+), 7 deletions(-) create mode 100644 install/db/160-database.php diff --git a/admin/include/functions.php b/admin/include/functions.php index 3d8ffa756..f417a3fc4 100644 --- a/admin/include/functions.php +++ b/admin/include/functions.php @@ -1888,6 +1888,129 @@ function compare_image_tag_lists($taglist_before, $taglist_after) return $images_to_update; } +/** + * Instead of associating images to categories, add them in the lounge, waiting for take-off. + * + * @since 12 + * @param array $images - list of image ids + * @param array $categories - list of category ids + */ +function fill_lounge($images, $categories) +{ + $inserts = array(); + foreach ($categories as $category_id) + { + foreach ($images as $image_id) + { + $inserts[] = array( + 'image_id' => $image_id, + 'category_id' => $category_id, + ); + } + } + + if (count($inserts)) + { + mass_inserts( + LOUNGE_TABLE, + array_keys($inserts[0]), + $inserts + ); + } +} + +/** + * Move images from the lounge to the categories they were intended for. + * + * @since 12 + * @param boolean $invalidate_user_cache + * @return int number of images moved + */ +function empty_lounge($invalidate_user_cache=true) +{ + global $logger; + + if (isset($conf['empty_lounge_running'])) + { + list($running_exec_id, $running_exec_start_time) = explode('-', $conf['empty_lounge_running']); + if (time() - $running_exec_start_time > 60) + { + $logger->debug(__FUNCTION__.', exec='.$running_exec_id.', timeout stopped by another call to the function'); + conf_delete_param('empty_lounge_running'); + } + } + + $exec_id = generate_key(4); + $logger->debug(__FUNCTION__.', exec='.$exec_id.', begins'); + + // if lounge is already being emptied, skip + $query = ' +INSERT IGNORE + INTO '.CONFIG_TABLE.' + SET param="empty_lounge_running" + , value="'.$exec_id.'-'.time().'" +;'; + pwg_query($query); + + list($empty_lounge_running) = pwg_db_fetch_row(pwg_query('SELECT value FROM '.CONFIG_TABLE.' WHERE param = "empty_lounge_running"')); + list($running_exec_id,) = explode('-', $empty_lounge_running); + + if ($running_exec_id != $exec_id) + { + $logger->debug(__FUNCTION__.', exec='.$exec_id.', skip'); + return; + } + $logger->debug(__FUNCTION__.', exec='.$exec_id.' wins the race and gets the token!'); + sleep(5); + + $max_image_id = 0; + + $query = ' +SELECT + image_id, + category_id + FROM '.LOUNGE_TABLE.' + ORDER BY category_id ASC, image_id ASC +;'; + + $rows = query2array($query); + + $images = array(); + foreach ($rows as $idx => $row) + { + if ($row['image_id'] > $max_image_id) + { + $max_image_id = $row['image_id']; + } + + $images[] = $row['image_id']; + + if (!isset($rows[$idx+1]) or $rows[$idx+1]['category_id'] != $row['category_id']) + { + // if we're at the end of the loop OR if category changes + associate_images_to_categories($images, array($row['category_id'])); + $images = array(); + } + } + + $query = ' +DELETE + FROM '.LOUNGE_TABLE.' + WHERE image_id <= '.$max_image_id.' +;'; + pwg_query($query); + + if ($invalidate_user_cache) + { + invalidate_user_cache(); + } + + conf_delete_param('empty_lounge_running'); + + $logger->debug(__FUNCTION__.', exec='.$exec_id.', ends'); + return count($rows); +} + /** * Associate a list of images to a list of categories. * The function will not duplicate links and will preserve ranks. @@ -3140,12 +3263,28 @@ SELECT path */ function get_orphans() { + // exclude images in the lounge + $query = ' +SELECT + image_id + FROM '.LOUNGE_TABLE.' +;'; + $lounged_ids = query2array($query, null, 'image_id'); + $query = ' SELECT id FROM '.IMAGES_TABLE.' LEFT JOIN '.IMAGE_CATEGORY_TABLE.' ON id = image_id - WHERE category_id is null + WHERE category_id is null'; + + if (count($lounged_ids) > 0) + { + $query .= ' + AND id NOT IN ('.implode(',', $lounged_ids).')'; + } + + $query.= ' ORDER BY id ASC ;'; diff --git a/admin/include/functions_upload.inc.php b/admin/include/functions_upload.inc.php index eed0a236e..5d3d1cb96 100644 --- a/admin/include/functions_upload.inc.php +++ b/admin/include/functions_upload.inc.php @@ -362,12 +362,31 @@ SELECT pwg_activity('photo', $image_id, 'add'); } + if (!isset($conf['lounge_active'])) + { + conf_update_param('lounge_active', false, true); + } + + if (!$conf['lounge_active']) + { + // check if we need to use the lounge from now + list($nb_photos) = pwg_db_fetch_row(pwg_query('SELECT COUNT(*) FROM '.IMAGES_TABLE.';')); + if ($nb_photos >= $conf['lounge_activate_threshold']) + { + conf_update_param('lounge_active', true, true); + } + } + if (isset($categories) and count($categories) > 0) { - associate_images_to_categories( - array($image_id), - $categories - ); + if ($conf['lounge_active']) + { + fill_lounge(array($image_id), $categories); + } + else + { + associate_images_to_categories(array($image_id), $categories); + } } // update metadata from the uploaded file (exif/iptc) @@ -377,7 +396,10 @@ SELECT } sync_metadata(array($image_id)); - invalidate_user_cache(); + if (!$conf['lounge_active']) + { + invalidate_user_cache(); + } // cache a derivative $query = ' diff --git a/admin/maintenance.php b/admin/maintenance.php index f2a3e6b3c..b2765c268 100644 --- a/admin/maintenance.php +++ b/admin/maintenance.php @@ -77,6 +77,7 @@ switch ($action) } case 'user_cache' : { + empty_lounge(false); invalidate_user_cache(); break; } diff --git a/admin/themes/default/template/photos_add_direct.tpl b/admin/themes/default/template/photos_add_direct.tpl index 52cb21626..0f961520c 100644 --- a/admin/themes/default/template/photos_add_direct.tpl +++ b/admin/themes/default/template/photos_add_direct.tpl @@ -222,6 +222,14 @@ jQuery(document).ready(function(){ Piecon.reset(); + jQuery.ajax({ + url: "ws.php?format=json&method=pwg.images.emptyLounge", + type:"POST", + data: { + pwg_token: pwg_token + } + }); + jQuery(".selectAlbum, .selectFiles, #permissions, .showFieldset").hide(); jQuery(".infos").append(''); diff --git a/include/common.inc.php b/include/common.inc.php index c3e6cfe12..cbca46434 100644 --- a/include/common.inc.php +++ b/include/common.inc.php @@ -151,6 +151,8 @@ if (isset($conf['order_by_inside_category_custom'])) $conf['order_by_inside_category'] = $conf['order_by_inside_category_custom']; } +check_lounge(); + include(PHPWG_ROOT_PATH.'include/user.inc.php'); if (in_array( substr($user['language'],0,2), array('fr','it','de','es','pl','ru','nl','tr','da') ) ) diff --git a/include/config_default.inc.php b/include/config_default.inc.php index 246e84b5a..b7cc9ca4c 100644 --- a/include/config_default.inc.php +++ b/include/config_default.inc.php @@ -433,6 +433,17 @@ $conf['session_gc_probability'] = 1; // | debug/performance | // +-----------------------------------------------------------------------+ +// number of photos beyond which individual photos are added in the +// lounge, a temporary zone where photos wait before being "launched". +// 50k photos by default. +$conf['lounge_activate_threshold'] = 50000; + +// Lounge is automatically emptied (photos are being pushed to their +// albums) when the oldest one reaches this duration. Lounge can be emptied +// before, either manually or at the end of the upload. In seconds. +// 5 minutes by default. +$conf['lounge_max_duration'] = 5*60; + // show_queries : for debug purpose, show queries and execution times $conf['show_queries'] = false; diff --git a/include/constants.php b/include/constants.php index 94f45babf..9e97300f6 100644 --- a/include/constants.php +++ b/include/constants.php @@ -100,5 +100,7 @@ if (!defined('IMAGE_FORMAT_TABLE')) define('IMAGE_FORMAT_TABLE', $prefixeTable.'image_format'); if (!defined('ACTIVITY_TABLE')) define('ACTIVITY_TABLE', $prefixeTable.'activity'); +if (!defined('LOUNGE_TABLE')) + define('LOUNGE_TABLE', $prefixeTable.'lounge'); ?> diff --git a/include/functions.inc.php b/include/functions.inc.php index f5b69c744..2225197ff 100644 --- a/include/functions.inc.php +++ b/include/functions.inc.php @@ -2266,4 +2266,48 @@ function safe_version_compare($a, $b, $op=null) } } +/** + * Checks if the lounge needs to be emptied automatically. + * + * @since 12 + */ +function check_lounge() +{ + global $conf; + + if (!isset($conf['lounge_active']) or !$conf['lounge_active']) + { + return; + } + + if (isset($_REQUEST['method']) and in_array($_REQUEST['method'], array('pwg.images.upload', 'pwg.images.uploadAsync'))) + { + return; + } + + // is the oldest photo in the lounge older than lounge maximum waiting time? + $query = ' +SELECT + image_id, + date_available, + NOW() AS dbnow + FROM '.LOUNGE_TABLE.' + JOIN '.IMAGES_TABLE.' ON image_id = id + ORDER BY image_id ASC + LIMIT 1 +;'; + $voyagers = query2array($query); + if (count($voyagers)) + { + $voyager = $voyagers[0]; + $age = strtotime($voyager['dbnow']) - strtotime($voyager['date_available']); + + if ($age > $conf['lounge_max_duration']) + { + include_once(PHPWG_ROOT_PATH.'admin/include/functions.php'); + empty_lounge(); + } + } +} + ?> diff --git a/include/functions_category.inc.php b/include/functions_category.inc.php index 64f1137a8..d40581e6d 100644 --- a/include/functions_category.inc.php +++ b/include/functions_category.inc.php @@ -475,6 +475,11 @@ SELECT image_id */ function get_computed_categories(&$userdata, $filter_days=null) { + global $logger; + + $start_time = get_moment(); + $logger->debug(__FUNCTION__.' starts now'); + $query = 'SELECT c.id AS cat_id, id_uppercat'; $query.= ', global_rank'; // Count by date_available to avoid count null @@ -569,6 +574,9 @@ FROM '.CATEGORIES_TABLE.' as c } } } + + $logger->debug(__FUNCTION__.' ends now in '.get_elapsed_time($start_time, get_moment())); + return $cats; } diff --git a/include/ws_functions/pwg.images.php b/include/ws_functions/pwg.images.php index ef7286b9f..5ff3bb5cd 100644 --- a/include/ws_functions/pwg.images.php +++ b/include/ws_functions/pwg.images.php @@ -405,8 +405,10 @@ SELECT id, name, permalink, uppercats, global_rank, commentable } usort($related_categories, 'global_rank_compare'); - if (empty($related_categories)) + if (empty($related_categories) and !is_admin()) { + // photo might be in the lounge? or simply orphan. A standard user should not get + // info. An admin should still be able to get info. return new PwgError(401, 'Access denied'); } @@ -2094,6 +2096,21 @@ function ws_images_checkUpload($params, $service) return $ret; } +/** + * API method + * Empties the lounge, where photos may wait before taking off. + * @since 12 + * @param mixed[] $params + */ +function ws_images_emptyLounge($params, $service) +{ + include_once(PHPWG_ROOT_PATH.'admin/include/functions.php'); + + $ret = array('count' => empty_lounge()); + + return $ret; +} + /** * API method * add md5sum at photos, by block. Returns how md5sum were added and how many are remaining. diff --git a/install/db/160-database.php b/install/db/160-database.php new file mode 100644 index 000000000..1ff8e1fe3 --- /dev/null +++ b/install/db/160-database.php @@ -0,0 +1,26 @@ + diff --git a/install/piwigo_structure-mysql.sql b/install/piwigo_structure-mysql.sql index 30e49c34a..8e3c15674 100644 --- a/install/piwigo_structure-mysql.sql +++ b/install/piwigo_structure-mysql.sql @@ -260,6 +260,17 @@ CREATE TABLE `piwigo_languages` ( PRIMARY KEY (`id`) ) ENGINE=MyISAM; +-- +-- Table structure for table `piwigo_lounge` +-- + +DROP TABLE IF EXISTS `piwigo_lounge`; +CREATE TABLE `piwigo_lounge` ( + `image_id` mediumint(8) unsigned NOT NULL DEFAULT '0', + `category_id` smallint(5) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`image_id`,`category_id`) +) ENGINE=MyISAM; + -- -- Table structure for table `piwigo_old_permalinks` -- diff --git a/ws.php b/ws.php index bceda3a04..601fdae6b 100644 --- a/ws.php +++ b/ws.php @@ -769,6 +769,15 @@ function ws_addDefaultMethods( $arr ) array('admin_only'=>true) ); + $service->addMethod( + 'pwg.images.emptyLounge', + 'ws_images_emptyLounge', + null, + 'Empty lounge, where images may be waiting before taking off.', + $ws_functions_root . 'pwg.images.php', + array('admin_only'=>true) + ); + $service->addMethod( 'pwg.images.setInfo', 'ws_images_setInfo',