From 225b45f2b88d3193ec4012a589749c5814c42205 Mon Sep 17 00:00:00 2001 From: rvelices Date: Sun, 8 Jan 2012 19:25:52 +0000 Subject: feature 2548 multisize - added a page to build missing derivatives - browser driven, chained ws calls to retrieve urls, visual feedback of progress through slideshow git-svn-id: http://piwigo.org/svn/trunk@12865 68402e56-0260-453c-a942-63ccdbb3a9ee --- admin/derivatives_build.php | 30 ++++ admin/include/functions.php | 3 - admin/include/image.class.php | 1 + admin/themes/default/template/derivatives.tpl | 1 + .../themes/default/template/derivatives_build.tpl | 152 +++++++++++++++++++++ i.php | 47 +++++-- include/derivative.inc.php | 5 + include/ws_functions.inc.php | 148 +++++++++++++++----- themes/default/js/image.loader.js | 84 ++++++++++++ themes/default/template/picture_content.tpl | 4 +- ws.php | 25 +++- 11 files changed, 446 insertions(+), 54 deletions(-) create mode 100644 admin/derivatives_build.php create mode 100644 admin/themes/default/template/derivatives_build.tpl create mode 100644 themes/default/js/image.loader.js diff --git a/admin/derivatives_build.php b/admin/derivatives_build.php new file mode 100644 index 000000000..4203124ac --- /dev/null +++ b/admin/derivatives_build.php @@ -0,0 +1,30 @@ +assign('derivatives', array_keys(ImageStdParams::get_defined_type_map())); + +$template->set_filename('derivatives', 'derivatives_build.tpl'); +$template->assign_var_from_handle('ADMIN_CONTENT', 'derivatives'); +?> \ No newline at end of file diff --git a/admin/include/functions.php b/admin/include/functions.php index 59e59e5c2..84bae9154 100644 --- a/admin/include/functions.php +++ b/admin/include/functions.php @@ -200,11 +200,8 @@ SELECT $files = array(); $files[] = get_element_path($row); - if (!empty($row['representative_ext'])) { - $pi = pathinfo($row['path']); - $file_wo_ext = get_filename_wo_extension($pi['basename']); $files[] = original_to_representative( $files[0], $row['representative_ext']); } diff --git a/admin/include/image.class.php b/admin/include/image.class.php index 03cf419a4..c8e61a829 100644 --- a/admin/include/image.class.php +++ b/admin/include/image.class.php @@ -704,6 +704,7 @@ class image_gd implements imageInterface // Place the source image in the destination image imagecopy($cut, $ioverlay, 0, 0, 0, 0, $ow, $oh); imagecopymerge($this->image, $cut, $x, $y, 0, 0, $ow, $oh, $opacity); + imagedestroy($cut); return true; } diff --git a/admin/themes/default/template/derivatives.tpl b/admin/themes/default/template/derivatives.tpl index 59f6344a7..dcf52a4ec 100644 --- a/admin/themes/default/template/derivatives.tpl +++ b/admin/themes/default/template/derivatives.tpl @@ -22,6 +22,7 @@ {/literal}{/html_head} +

Build missing derivatives

{'Watermark'|@translate} diff --git a/admin/themes/default/template/derivatives_build.tpl b/admin/themes/default/template/derivatives_build.tpl new file mode 100644 index 000000000..6e0c8e35d --- /dev/null +++ b/admin/themes/default/template/derivatives_build.tpl @@ -0,0 +1,152 @@ +{html_head}{literal} + +{/literal}{/html_head} + +

+ + + + +

+
+

+ + + + + + + + + + + + + +
Errors0
Loaded0
Remaining0
+

+ +
+

+ +
+
+ +{combine_script id='iloader' load='footer' path='themes/default/js/image.loader.js'} + +{footer_script require='jquery.effects.slide'}{literal} + +var loader = new ImageLoader( {onChanged: loaderChanged} ) + , pending_next_page = null + , last_image_show_time = 0 + , allDoneDfd, urlDfd; + +function start() { + allDoneDfd = jQuery.Deferred(); + urlDfd = jQuery.Deferred(); + + allDoneDfd.always( function() { + jQuery("#startLink").attr('disabled', false).css("opacity", 1); + jQuery("#pauseLink,#stopLink").attr('disabled', true).css("opacity", 0.5); + } ); + + urlDfd.always( function() { + if (loader.remaining()==0) + allDoneDfd.resolve(); + } ); + + jQuery("#startLink").attr('disabled', true).css("opacity", 0.5); + jQuery("#pauseLink,#stopLink").attr('disabled', false).css("opacity", 1); + + loader.pause(false); + updateStats(); + getUrls(); +} + +function pause() { + loader.pause( !loader.pause() ); +} + +function stop() { + loader.clear(); + urlDfd.resolve(); +} + +function getUrls(page_token) { + data = {max_urls: 500, types: []}; + jQuery.each(jQuery("#types").serializeArray(), function(i, t) { + data.types.push( t.value ); + } ); + + if (page_token) + data['prev_page'] = page_token; + jQuery.post( '{/literal}{$ROOT_URL}{literal}ws.php?format=json&method=pwg.getMissingDerivatives', + data, wsData, "json").fail( wsError ); +} + +function wsData(data) { + if (!data.stat || data.stat != "ok") { + wsError(); + return; + } + loader.add( data.result.urls ); + if (data.result.next_page) { + if (loader.pause() || loader.remaining() > 100) { + pending_next_page = data.result.next_page; + } + else { + getUrls(data.result.next_page); + } + } +} + +function wsError() { + urlDfd.reject(); +} + +function updateStats() { + jQuery("#loaded").text( loader.loaded ); + jQuery("#errors").text( loader.errors ); + jQuery("#remaining").text( loader.remaining() ); +} + +function loaderChanged(type, img) { + updateStats(); + if (img) { + if (type==="load") { + var now = jQuery.now(); + if (now - last_image_show_time > 3000) { + last_image_show_time = now; + var h=img.height, url=img.src; + jQuery("#feedbackWrap").hide("slide", {direction:'down'}, function() { + last_image_show_time = jQuery.now(); + if (h > 300 ) + jQuery("#feedbackImg").attr("height", 300); + else + jQuery("#feedbackImg").removeAttr("height"); + jQuery("#feedbackImg").attr("src", url); + jQuery("#feedbackWrap").show("slide", {direction:'up'} ); + } ); + } + } + else { + jQuery("#errorList").prepend( ''+img.src+'' + "
"); + } + } + if (pending_next_page && 100 > loader.remaining() ) { + getUrls(pending_next_page); + pending_next_page = null; + } + else if (loader.remaining() == 0 && (urlDfd.isResolved() || urlDfd.isRejected())) { + allDoneDfd.resolve(); + } +} +{/literal}{/footer_script} \ No newline at end of file diff --git a/i.php b/i.php index 5ab032c90..fb2dbc93a 100644 --- a/i.php +++ b/i.php @@ -108,6 +108,10 @@ function parse_request() else { $req = $_SERVER["QUERY_STRING"]; + if ($pos=strpos($req, '&')) + { + $req = substr($req, 0, $pos); + } /*foreach (array_keys($_GET) as $keynum => $key) { $req = $key; @@ -167,12 +171,20 @@ function parse_request() { try { - $page['derivative_params'] = DerivativeParams::from_url_tokens($deriv); + $params = $page['derivative_params'] = DerivativeParams::from_url_tokens($deriv); } catch (Exception $e) { ierror($e->getMessage(), 400); } + if ($params->sizing->ideal_size[0] < 20 or $params->sizing->ideal_size[1] < 20) + { + ierror('Invalid size', 400); + } + if ($params->sizing->max_crop < 0 or $params->sizing->max_crop > 1) + { + ierror('Invalid crop', 400); + } } if ($req[0]!='g' && $req[0]!='u') @@ -224,14 +236,6 @@ parse_request(); //var_export($page); $params = $page['derivative_params']; -if ($params->sizing->ideal_size[0] < 20 or $params->sizing->ideal_size[1] < 20) -{ - ierror('Invalid size', 400); -} -if ($params->sizing->max_crop < 0 or $params->sizing->max_crop > 1) -{ - ierror('Invalid crop', 400); -} $src_mtime = @filemtime($page['src_path']); if ($src_mtime === false) @@ -250,7 +254,12 @@ if ($derivative_mtime === false or $expires=false; $now = time(); -if ( $now > (max($src_mtime, $params->last_mod_time) + 24*3600) ) +if ( isset($_GET['b']) ) +{ + $expires = $now + 100; + header("Cache-control: no-store, max-age=100"); +} +elseif ( $now > (max($src_mtime, $params->last_mod_time) + 24*3600) ) {// somehow arbitrary - if derivative params or src didn't change for the last 24 hours, we send an expire header for several days $expires = $now + 10*24*3600; } @@ -267,6 +276,10 @@ if (!$need_generate) send_derivative($expires); } +if (!mkgetdir(dirname($page['derivative_path']))) +{ + ierror("dir create error", 500); +} include_once(PHPWG_ROOT_PATH . 'admin/include/image.class.php'); @@ -275,11 +288,6 @@ set_time_limit(0); $image = new pwg_image($page['src_path']); -if (!mkgetdir(dirname($page['derivative_path']))) -{ - ierror("dir create error", 500); -} - $changes = 0; // todo rotate @@ -325,6 +333,15 @@ if ($params->use_watermark) if ($wm->xrepeat) { // todo + $pad = $wm_size[0] + max(30, round($wm_size[0]/4)); + for($i=-$wm->xrepeat; $i<=$wm->xrepeat; $i++) + { + if (!$i) continue; + $x2 = $x + $i * $pad; + if ($x2>=0 && $x2+$wm_size[0]<$d_size[0]) + if (!$image->compose($wm_image, $x2, $y, $wm->opacity)) + break; + } } } $wm_image->destroy(); diff --git a/include/derivative.inc.php b/include/derivative.inc.php index 0c888f693..d15e7eff3 100644 --- a/include/derivative.inc.php +++ b/include/derivative.inc.php @@ -219,6 +219,11 @@ final class DerivativeImage } } + function get_path() + { + return PHPWG_ROOT_PATH.$this->rel_path; + } + function get_url() { return get_root_url().$this->rel_url; diff --git a/include/ws_functions.inc.php b/include/ws_functions.inc.php index b0ac24362..068cf84b3 100644 --- a/include/ws_functions.inc.php +++ b/include/ws_functions.inc.php @@ -92,9 +92,9 @@ function ws_std_image_sql_filter( $params, $tbl_name='' ) { $clauses[] = $tbl_name.'width/'.$tbl_name.'height<='.$params['f_max_ratio']; } - if ( $params['f_with_thumbnail'] ) + if (is_numeric($params['f_max_level']) ) { - $clauses[] = $tbl_name.'tn_ext IS NOT NULL'; + $clauses[] = $tbl_name.'level <= '.$params['f_max_level']; } return $clauses; } @@ -147,7 +147,7 @@ function ws_std_image_sql_order( $params, $tbl_name='' ) function ws_std_get_urls($image_row) { $ret = array(); - + $src_image = new SrcImage($image_row); global $user; @@ -155,7 +155,7 @@ function ws_std_get_urls($image_row) { $ret['element_url'] = get_element_url($image_row); } - + $derivatives = DerivativeImage::get_all($src_image); $derivatives_arr = array(); foreach($derivatives as $type=>$derivative) @@ -179,6 +179,92 @@ function ws_std_get_image_xml_attributes() ); } +function ws_getMissingDerivatives($params, &$service) +{ + if (!is_admin()) + { + return new PwgError(403, 'Forbidden'); + } + + if ( empty($params['types']) ) + { + $types = array_keys(ImageStdParams::get_defined_type_map()); + } + else + { + $types = array_intersect(array_keys(ImageStdParams::get_defined_type_map()), $params['types']); + if (count($types)==0) + { + return new PwgError(WS_ERR_INVALID_PARAM, "Invalid types"); + } + } + + if ( ($max_urls = intval($params['max_urls'])) <= 0) + { + return new PwgError(WS_ERR_INVALID_PARAM, "Invalid max_urls"); + } + + list($max_id, $image_count) = pwg_db_fetch_row( pwg_query('SELECT MAX(id)+1, COUNT(*) FROM '.IMAGES_TABLE) ); + $start_id = intval($params['prev_page']); + if ($start_id<=0) + { + $start_id = $max_id; + } + + $uid = '&b='.time(); + global $conf; + $conf['question_mark_in_urls'] = $conf['php_extension_in_urls'] = true; + $conf['derivative_url_style']=2; //script + + $qlimit = min(5000, ceil(max($image_count/500, $max_urls/count($types)))); + $where_clauses = ws_std_image_sql_filter( $params, '' ); + $where_clauses[] = 'idis_mimetype()) + continue; + foreach($types as $type) + { + $derivative = new DerivativeImage($type, $src_image); + if ($type != $derivative->get_type()) + continue; + if (@filemtime($derivative->get_path())===false) + { + $urls[] = $derivative->get_url().$uid; + } + } + if (count($urls)>=$max_urls && !$is_last) + break; + } + if ($is_last) + { + $start_id = 0; + } + }while (count($urls)<$max_urls && $start_id); + + $ret = array(); + if ($start_id) + { + $ret['next_page']=$start_id; + } + $ret['urls']=$urls; + return $ret; +} + /** * returns PWG version (web service method) */ @@ -514,7 +600,7 @@ SELECT id, name, permalink, uppercats, global_rank, id_uppercat, $categories = array(); $user_representative_updates_for = array(); // management of the album thumbnail -- stops here - + $cats = array(); while ($row = pwg_db_fetch_assoc($result)) { @@ -552,7 +638,7 @@ SELECT id, name, permalink, uppercats, global_rank, id_uppercat, ); // management of the album thumbnail -- starts here - // + // // on branch 2.3, the algorithm is duplicated from // include/category_cats, but we should use a common code for Piwigo 2.4 // @@ -603,14 +689,14 @@ SELECT id, name, permalink, uppercats, global_rank, id_uppercat, } } } - + if (isset($image_id)) { if ($conf['representative_cache_on_subcats'] and $row['user_representative_picture_id'] != $image_id) { $user_representative_updates_for[ $user['id'].'#'.$row['id'] ] = $image_id; } - + $row['representative_picture_id'] = $image_id; array_push($image_ids, $image_id); array_push($categories, $row); @@ -650,31 +736,31 @@ SELECT id, path, representative_ext, level // * find a random photo matching user permissions // * register it at user_representative_picture_id // * set it as the representative_picture_id for the category - + foreach ($categories as &$category) { if ($row['id'] == $category['representative_picture_id']) { // searching a random representant among elements in sub-categories $image_id = get_random_image_in_category($category); - + if (isset($image_id) and !in_array($image_id, $image_ids)) { array_push($new_image_ids, $image_id); } - + if ($conf['representative_cache_on_level']) { $user_representative_updates_for[ $user['id'].'#'.$category['id'] ] = $image_id; } - + $category['representative_picture_id'] = $image_id; } } unset($category); } } - + if (count($new_image_ids) > 0) { $query = ' @@ -696,11 +782,11 @@ SELECT id, path, representative_ext if (!$params['public'] and count($user_representative_updates_for)) { $updates = array(); - + foreach ($user_representative_updates_for as $user_cat => $image_id) { list($user_id, $cat_id) = explode('#', $user_cat); - + array_push( $updates, array( @@ -735,7 +821,7 @@ SELECT id, path, representative_ext unset($cat['count_images']); unset($cat['count_categories']); } - unset($cat); + unset($cat); // management of the album thumbnail -- stops here if ($params['tree_output']) @@ -1530,7 +1616,7 @@ function add_file($file_path, $type, $original_sum, $file_sum) // check dumped thumbnail md5 $dumped_md5 = md5_file($file_path); - if ($dumped_md5 != $file_sum) + if ($dumped_md5 != $file_sum) { return new PwgError(500, '[add_file] '.$type.' transfer failed'); } @@ -1665,16 +1751,16 @@ SELECT if ($params['resize']) { ws_logfile('[pwg.images.add] resize activated'); - + // temporary file path $type = 'file'; $file_path = $conf['upload_dir'].'/buffer/'.$params['original_sum'].'-'.$type; - + merge_chunks($file_path, $params['original_sum'], $type); chmod($file_path, 0644); include_once(PHPWG_ROOT_PATH.'admin/include/functions_upload.inc.php'); - + $image_id = add_uploaded_file( $file_path, $params['original_filename'] @@ -1703,11 +1789,11 @@ SELECT $random_string = substr($params['file_sum'], 0, 8); $filename_wo_ext = $date_string.'-'.$random_string; $file_path = $upload_dir.'/'.$filename_wo_ext.'.jpg'; - + // add files $file_infos = add_file($file_path, 'file', $params['original_sum'], $params['file_sum']); $thumb_infos = add_file($file_path, 'thumb', $params['original_sum'], $params['thumbnail_sum']); - + if (isset($params['high_sum'])) { $high_infos = add_file($file_path, 'high', $params['original_sum'], $params['high_sum']); @@ -1762,7 +1848,7 @@ SELECT $update[$key] = $params[$key]; } } - + if (count(array_keys($update)) > 0) { single_update( @@ -1773,7 +1859,7 @@ SELECT } $url_params = array('image_id' => $image_id); - + // let's add links between the image and the categories if (isset($params['categories'])) { @@ -1781,7 +1867,7 @@ SELECT if (preg_match('/^\d+/', $params['categories'], $matches)) { $category_id = $matches[0]; - + $query = ' SELECT id, name, permalink FROM '.CATEGORIES_TABLE.' @@ -1789,7 +1875,7 @@ SELECT id, name, permalink ;'; $result = pwg_query($query); $category = pwg_db_fetch_assoc($result); - + $url_params['section'] = 'categories'; $url_params['category'] = $category; } @@ -1957,16 +2043,16 @@ function ws_rates_delete($params, &$service) { return new PwgError(WS_ERR_INVALID_PARAM, 'Invalid user_id'); } - + $query = ' DELETE FROM '.RATE_TABLE.' WHERE user_id='.$user_id; - + if (!empty($params['anonymous_id'])) { $query .= ' AND anonymous_id=\''.$params['anonymous_id'].'\''; } - + $changes = pwg_db_changes(pwg_query($query)); if ($changes) { @@ -3274,7 +3360,7 @@ SELECT id, path, tn_ext, has_high, width, height { return new PwgError(403, "image can't be resized"); } - + $hd_path = get_high_path($image); if (empty($image['has_high']) or !file_exists($hd_path)) @@ -3284,7 +3370,7 @@ SELECT id, path, tn_ext, has_high, width, height $hd_path = file_path_for_type($image_path, 'high'); $hd_dir = dirname($hd_path); prepare_directory($hd_dir); - + rename($image_path, $hd_path); $hd_infos = pwg_image_infos($hd_path); diff --git a/themes/default/js/image.loader.js b/themes/default/js/image.loader.js new file mode 100644 index 000000000..cd8b9cc52 --- /dev/null +++ b/themes/default/js/image.loader.js @@ -0,0 +1,84 @@ + +function ImageLoader(opts) { + this.opts = jQuery.extend( { + maxRequests: 6, + onChanged: jQuery.noop + }, opts||{} ); +} + +ImageLoader.prototype = { + loaded: 0, + errors: 0, + errorEma: 0, + + pause: false, + + current: [], + queue: [], + pool: [], + + remaining: function() { + return this.current.length + this.queue.length; + }, + + add: function(urls) { + this.queue = this.queue.concat( urls ); + this._fireChanged("add"); + this._checkQueue(); + }, + + clear: function() { + this.queue.length = 0; + while (this.current.length) + jQuery( this.current.pop() ).unbind(); + this.loaded = this.errors = this.errorEma = 0; + }, + + pause: function(val) { + if (val !== undefined) + { + this.paused = val; + this._checkQueue(); + } + return this.paused; + }, + + _checkQueue: function() { + while (!this.paused + && this.queue.length + && this.current.length < this.opts.maxRequests) + { + this._processOne( this.queue.shift() ); + } + }, + + _processOne: function(url) { + var img = this.pool.shift() || new Image; + this.current.push(img); + var that = this; + jQuery(img).bind( "load error abort", function(e) { + //img.onload = function(e) { + jQuery(img).unbind(); + img.onload=null; + that.current.splice(jQuery.inArray(img, that.current), 1); + if (e.type==="load") { + that.loaded++; + that.errorEma *= 0.9; + } + else { + that.errors++; + that.errorEma++; + if (that.errorEma>=20 && that.errorEma<21) + that.paused = true; + } + that._fireChanged(e.type, img); + that._checkQueue(); + that.pool.push(img); + } ); + img.src = url; + }, + + _fireChanged: function(type, img) { + this.opts.onChanged(type, img); + } +} \ No newline at end of file diff --git a/themes/default/template/picture_content.tpl b/themes/default/template/picture_content.tpl index 47dcd5560..cc85a1e05 100644 --- a/themes/default/template/picture_content.tpl +++ b/themes/default/template/picture_content.tpl @@ -32,13 +32,13 @@ function toggleDerivativeSwitchBox() elt.style.display="none"; } {/literal}{/footer_script} -{$current.selected_derivative->get_type()|@translate} +{$current.selected_derivative->get_type()|@translate} {/if} \ No newline at end of file diff --git a/ws.php b/ws.php index 4319bada9..d42b8f828 100644 --- a/ws.php +++ b/ws.php @@ -69,7 +69,7 @@ function ws_addDefaultMethods( $arr ) 'f_max_date_created' => array( 'default'=> null ), 'f_min_ratio' => array( 'default'=> null ), 'f_max_ratio' => array( 'default'=> null ), - 'f_with_thumbnail' => array( 'default'=> false ), + 'f_max_level' => array( 'default'=> null ), ), 'Returns elements for the corresponding categories.
cat_id can be empty if recursive is true. Can be sent as an array. @@ -86,6 +86,25 @@ function ws_addDefaultMethods( $arr ) ), 'retrieves a list of categories (tree_output option only compatible with json/php output format' ); + $service->addMethod('pwg.getMissingDerivatives', 'ws_getMissingDerivatives', + array( + 'types' => array( 'default'=>array(), 'flags'=>WS_PARAM_FORCE_ARRAY), + 'max_urls' => array( 'default' => 200 ), + 'prev_page' => array( 'default'=> null), + 'f_min_rate' => array( 'default'=> null ), + 'f_max_rate' => array( 'default'=> null ), + 'f_min_hit' => array( 'default'=> null ), + 'f_max_hit' => array( 'default'=> null ), + 'f_min_date_available' => array( 'default'=> null ), + 'f_max_date_available' => array( 'default'=> null ), + 'f_min_date_created' => array( 'default'=> null ), + 'f_max_date_created' => array( 'default'=> null ), + 'f_min_ratio' => array( 'default'=> null ), + 'f_max_ratio' => array( 'default'=> null ), + 'f_max_level' => array( 'default'=> null ), + ), + 'retrieves a list of derivatives to build' ); + $service->addMethod('pwg.images.addComment', 'ws_images_addComment', array( 'image_id' => array(), @@ -129,7 +148,7 @@ function ws_addDefaultMethods( $arr ) 'f_max_date_created' => array( 'default'=> null ), 'f_min_ratio' => array( 'default'=> null ), 'f_max_ratio' => array( 'default'=> null ), - 'f_with_thumbnail' => array( 'default'=> false ), + 'f_max_level' => array( 'default'=> null ), ), 'Returns elements for the corresponding query search.' ); @@ -192,7 +211,7 @@ function ws_addDefaultMethods( $arr ) 'f_max_date_created' => array( 'default'=> null ), 'f_min_ratio' => array( 'default'=> null ), 'f_max_ratio' => array( 'default'=> null ), - 'f_with_thumbnail' => array( 'default'=> false ), + 'f_max_level' => array( 'default'=> null ), ), 'Returns elements for the corresponding tags. Note that tag_id, tag_url_name, tag_name an be arrays. Fill at least one of them. ' ); -- cgit v1.2.3