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('
- '+sprintf(photosUploaded_label, uploadedPhotos.length)+'
');
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',