From 7ba15740434b476e8aef0253919f19ba37e07dc7 Mon Sep 17 00:00:00 2001 From: plegall Date: Mon, 27 Dec 2004 14:30:49 +0000 Subject: - admin/update : filesystem synchronization process completely rewritten. How to speed up sync ? by avoiding recursivity ! - admin/update : option to display verbose information details - admin/update : option to simulate only. No database insert, delete or update will be made - bug fixed : in admin/cat_list, if you delete a virtual category, you may create a gap in the rank list. This gap will generate errors when trying to move a category on this gap. Fixed by calling ordering and update_global_rank at category deletion. - admin/cat_list, only one query to insert a new virtual category (no need of a second query to update uppercats and global_rank) - for a given category, even if empty, the representing element must not be the one of a forbidden category for the current user - generation time optionnaly displayed on the bottom of each page becomes more price : number of SQL queries and SQL time added. git-svn-id: http://piwigo.org/svn/trunk@659 68402e56-0260-453c-a942-63ccdbb3a9ee --- admin/cat_list.php | 52 ++-- admin/update.php | 810 +++++++++++++++++++++++++---------------------------- 2 files changed, 406 insertions(+), 456 deletions(-) (limited to 'admin') diff --git a/admin/cat_list.php b/admin/cat_list.php index 2f7834730..341577a3d 100644 --- a/admin/cat_list.php +++ b/admin/cat_list.php @@ -43,10 +43,10 @@ $navigation = $lang['home']; // request to delete a virtual category if (isset($_GET['delete']) and is_numeric($_GET['delete'])) { - $to_delete_categories = array(); - array_push($to_delete_categories,$_GET['delete']); - delete_categories($to_delete_categories); + delete_categories(array($_GET['delete'])); array_push($infos, $lang['cat_virtual_deleted']); + ordering(); + update_global_rank(); } // request to add a virtual category else if (isset($_POST['submit'])) @@ -76,15 +76,27 @@ SELECT id,uppercats,global_rank,visible,status 'global_rank' => $row['global_rank']); } + // what will be the inserted id ? + $query = ' +SELECT MAX(id)+1 + FROM '.CATEGORIES_TABLE.' +;'; + list($next_id) = mysql_fetch_array(pwg_query($query)); + $insert = array(); + $insert{'id'} = $next_id++; $insert{'name'} = $_POST['virtual_name']; $insert{'rank'} = $_POST['rank']; $insert{'commentable'} = $conf['newcat_default_commentable']; - $insert{'uploadable'} = $conf['newcat_default_uploadable']; + + // a virtual category can't be uploadable + $insert{'uploadable'} = 'false'; if (isset($parent)) { $insert{'id_uppercat'} = $parent{'id'}; + $insert{'uppercats'} = $parent{'uppercats'}.','.$insert{'id'}; + $insert{'global_rank'} = $parent{'global_rank'}.'.'.$insert{'rank'}; // at creation, must a category be visible or not ? Warning : if // the parent category is invisible, the category is automatically // create invisible. (invisible = locked) @@ -112,40 +124,18 @@ SELECT id,uppercats,global_rank,visible,status { $insert{'visible'} = $conf['newcat_default_visible']; $insert{'status'} = $conf['newcat_default_status']; + $insert{'uppercats'} = $insert{'id'}; + $insert{'global_rank'} = $insert{'rank'}; } $inserts = array($insert); // we have then to add the virtual category - $dbfields = array('site_id','name','id_uppercat','rank','commentable', - 'uploadable','visible','status'); + $dbfields = array('id','site_id','name','id_uppercat','rank', + 'commentable','uploadable','visible','status', + 'uppercats','global_rank'); mass_inserts(CATEGORIES_TABLE, $dbfields, $inserts); - - // And last we update the uppercats - $query = ' -SELECT MAX(id) - FROM '.CATEGORIES_TABLE.' -;'; - $my_id = array_pop(mysql_fetch_array(pwg_query($query))); - $query = ' -UPDATE '.CATEGORIES_TABLE; - if (isset($parent)) - { - $query.= " - SET uppercats = CONCAT('".$parent['uppercats']."',',',id) - , global_rank = CONCAT('".$parent['global_rank']."','.',rank)"; - } - else - { - $query.= ' - SET uppercats = id - , global_rank = id'; - } - $query.= ' - WHERE id = '.$my_id.' -;'; - pwg_query($query); array_push($infos, $lang['cat_virtual_added']); } } diff --git a/admin/update.php b/admin/update.php index b59f6e7b5..9926f24ed 100644 --- a/admin/update.php +++ b/admin/update.php @@ -25,504 +25,445 @@ // | USA. | // +-----------------------------------------------------------------------+ -if( !defined("PHPWG_ROOT_PATH") ) +if (!defined('PHPWG_ROOT_PATH')) { - die ("Hacking attempt!"); + die ('Hacking attempt!'); } include_once( PHPWG_ROOT_PATH.'admin/include/isadmin.inc.php'); define('CURRENT_DATE', date('Y-m-d')); +$error_labels = array('PWG-UPDATE-1' => $lang['update_wrong_dirname_short'], + 'PWG-UPDATE-2' => $lang['update_missing_tn_short']); +$errors = array(); +$infos = array(); // +-----------------------------------------------------------------------+ -// | functions | +// | directories / categories | // +-----------------------------------------------------------------------+ - -function insert_local_category($id_uppercat) +if (isset($_POST['submit']) + and ($_POST['sync'] == 'dirs' or $_POST['sync'] == 'files')) { - global $conf, $page, $user, $lang, $counts; - - $uppercats = ''; - $output = ''; - - // 0. retrieving informations on the category to display - $cat_directory = PHPWG_ROOT_PATH.'galleries'; - if (is_numeric($id_uppercat)) - { - $query = ' -SELECT id,name,uppercats,dir,visible,status - FROM '.CATEGORIES_TABLE.' - WHERE id = '.$id_uppercat.' -;'; - $row = mysql_fetch_array(pwg_query($query)); - $parent = array('id' => $row['id'], - 'name' => $row['name'], - 'dir' => $row['dir'], - 'uppercats' => $row['uppercats'], - 'visible' => $row['visible'], - 'status' => $row['status']); - - $upper_array = explode( ',', $parent['uppercats']); + $counts['new_categories'] = 0; + $counts['del_categories'] = 0; + $counts['del_elements'] = 0; + $counts['new_elements'] = 0; - $local_dir = ''; + // shall we simulate only + if (isset($_POST['simulate']) and $_POST['simulate'] == 1) + { + $simulate = true; + } + else + { + $simulate = false; + } + + $start = get_moment(); + // which categories to update ? + $cat_ids = array(); - $database_dirs = array(); - $query = ' -SELECT id,dir + $query = ' +SELECT id, uppercats, global_rank, status, visible FROM '.CATEGORIES_TABLE.' - WHERE id IN ('.$parent['uppercats'].') -;'; - $result = pwg_query($query); - while ($row = mysql_fetch_array($result)) - { - $database_dirs[$row['id']] = $row['dir']; - } - foreach ($upper_array as $id) + WHERE dir IS NOT NULL + AND site_id = 1'; + if (isset($_POST['cat']) and is_numeric($_POST['cat'])) + { + if (isset($_POST['subcats-included']) and $_POST['subcats-included'] == 1) { - $local_dir.= $database_dirs[$id].'/'; + $query.= ' + AND uppercats REGEXP \'(^|,)'.$_POST['cat'].'(,|$)\' +'; } - - $cat_directory.= '/'.$local_dir; - - // 1. display the category name to update - $output = ''; + array_push($to_delete, $db_fulldirs[$fulldir]); + unset($db_fulldirs[$fulldir]); + array_push($infos, array('path' => $fulldir, + 'info' => $lang['update_research_deleted'])); } - return $output; -} - -function insert_local_element($dir, $category_id) -{ - global $lang,$conf,$counts; - - $output = ''; - - // fs means FileSystem : $fs_files contains files in the filesystem found - // in $dir that can be managed by PhpWebGallery (see get_pwg_files - // function), $fs_thumbnails contains thumbnails, $fs_representatives - // contains potentially representative pictures for non picture files - $fs_files = get_pwg_files($dir); - $fs_thumbnails = get_thumb_files($dir); - $fs_representatives = get_representative_files($dir); - - // element deletion - $to_delete_elements = array(); - // deletion of element if the correspond file doesn't exist anymore - $query = ' -SELECT id,file - FROM '.IMAGES_TABLE.' - WHERE storage_category_id = '.$category_id.' -;'; - $result = pwg_query($query); - while ($row = mysql_fetch_array($result)) + if (count($to_delete) > 0) { - if (!in_array($row['file'], $fs_files)) + if (!$simulate) { - $output.= $row['file']; - $output.= ' '; - $output.= $lang['update_disappeared'].'
'; - array_push($to_delete_elements, $row['id']); + delete_categories($to_delete); } + $counts['del_categories'] = count($to_delete); } - // in picture case, we also delete the element if the thumbnail doesn't - // existe anymore - $query = ' -SELECT id,file,tn_ext + + echo get_elapsed_time($start, get_moment()); + echo ' for new method scanning directories
'; +} +// +-----------------------------------------------------------------------+ +// | files / elements | +// +-----------------------------------------------------------------------+ +if (isset($_POST['submit']) and $_POST['sync'] == 'files') +{ + $start_files = get_moment(); + $start= $start_files; + + $fs = get_fs($basedir); + + echo get_elapsed_time($start, get_moment()); + echo ' for get_fs
'; + + $cat_ids = array_diff(array_keys($db_categories), $to_delete); + + $db_elements = array(); + $db_unvalidated = array(); + + if (count($cat_ids) > 0) + { + $query = ' +SELECT id, path FROM '.IMAGES_TABLE.' - WHERE storage_category_id = '.$category_id.' - AND ('.implode(' OR ', - array_map( - create_function('$s', 'return "file LIKE \'%".$s."\'";') - , $conf['picture_ext'])).') + WHERE storage_category_id IN ( +'.wordwrap(implode(', ', $cat_ids), 80, "\n").') ;'; - $result = pwg_query($query); - while ($row = mysql_fetch_array($result)) - { - $thumbnail = $conf['prefix_thumbnail']; - $thumbnail.= get_filename_wo_extension($row['file']); - $thumbnail.= '.'.$row['tn_ext']; - if (!in_array($thumbnail, $fs_thumbnails)) + $result = pwg_query($query); + while ($row = mysql_fetch_array($result)) { - $output.= $row['file']; - $output.= ' : '; - $output.= $lang['update_disappeared_tn'].'
'; - array_push($to_delete_elements, $row['id']); + $db_elements[$row['id']] = $row['path']; } - } - $to_delete_elements = array_unique($to_delete_elements); - if (count($to_delete_elements) > 0) - { - delete_elements($to_delete_elements); - } - - $registered_elements = array(); - $query = ' -SELECT file FROM '.IMAGES_TABLE.' - WHERE storage_category_id = '.$category_id.' + // searching the unvalidated waiting elements (they must not be taken into + // account) + $query = ' +SELECT file,storage_category_id + FROM '.WAITING_TABLE.' + WHERE storage_category_id IN ( +'.wordwrap(implode(', ', $cat_ids), 80, "\n").') + AND validated = \'false\' ;'; - $result = pwg_query($query); - while ($row = mysql_fetch_array($result)) - { - array_push($registered_elements, $row['file']); + $result = pwg_query($query); + while ($row = mysql_fetch_array($result)) + { + array_push( + $db_unvalidated, + array_search($row['storage_category_id'], + $db_fulldirs).'/'.$row['file'] + ); + } } - // unvalidated pictures are picture uploaded by users, but not validated - // by an admin (so not registered truly visible yet) - $unvalidated_pictures = array(); - + // next element id available $query = ' -SELECT file - FROM '.WAITING_TABLE.' - WHERE storage_category_id = '.$category_id.' - AND validated = \'false\' +SELECT MAX(id)+1 AS next_element_id + FROM '.IMAGES_TABLE.' ;'; - $result = pwg_query($query); - while ($row = mysql_fetch_array($result)) - { - array_push($unvalidated_pictures, $row['file']); - } + list($next_element_id) = mysql_fetch_array(pwg_query($query)); - // we only search among the picture present in the filesystem and not - // present in the database yet. If we know that this picture is known as - // an uploaded one but not validated, it's not tested neither - $unregistered_elements = array_diff($fs_files - ,$registered_elements - ,$unvalidated_pictures); + $start = get_moment(); + // because isset is one hundred time faster than in_array + $fs['thumbnails'] = array_flip($fs['thumbnails']); + $fs['representatives'] = array_flip($fs['representatives']); + $inserts = array(); + $insert_links = array(); - foreach ($unregistered_elements as $unregistered_element) + foreach (array_diff($fs['elements'], $db_elements, $db_unvalidated) as $path) { - if (preg_match('/^[a-zA-Z0-9-_.]+$/', $unregistered_element)) + $insert = array(); + // storage category must exist + $dirname = dirname($path); + if (!isset($db_fulldirs[$dirname])) { - $file_wo_ext = get_filename_wo_extension($unregistered_element); - $tn_ext = ''; - foreach ($conf['picture_ext'] as $ext) + continue; + } + $filename = basename($path); + if (!preg_match('/^[a-zA-Z0-9-_.]+$/', $filename)) + { + array_push($errors, array('path' => $path, 'type' => 'PWG-UPDATE-1')); + continue; + } + + // searching the thumbnail + $filename_wo_ext = get_filename_wo_extension($filename); + $tn_ext = ''; + $base_test = $dirname.'/thumbnail/'; + $base_test.= $conf['prefix_thumbnail'].$filename_wo_ext.'.'; + foreach ($conf['picture_ext'] as $ext) + { + $test = $base_test.$ext; + if (isset($fs['thumbnails'][$test])) { - $test = $conf['prefix_thumbnail'].$file_wo_ext.'.'.$ext; - if (!in_array($test, $fs_thumbnails)) - { - continue; - } - else - { - $tn_ext = $ext; - break; - } + $tn_ext = $ext; + break; + } + else + { + continue; } + } - // 2 cases : the element is a picture or not. Indeed, for a picture - // thumbnail is mandatory and for non picture element, thumbnail and - // representative is optionnal - if (in_array(get_extension($unregistered_element), $conf['picture_ext'])) + // 2 cases : the element is a picture or not. Indeed, for a picture + // thumbnail is mandatory and for non picture element, thumbnail and + // representative are optionnal + if (in_array(get_extension($filename), $conf['picture_ext'])) + { + // if we found a thumnbnail corresponding to our picture... + if ($tn_ext != '') { - // if we found a thumnbnail corresponding to our picture... - if ($tn_ext != '') - { - $insert = array(); - $insert['file'] = $unregistered_element; - $insert['storage_category_id'] = $category_id; - $insert['date_available'] = CURRENT_DATE; - $insert['tn_ext'] = $tn_ext; - $insert['path'] = $dir.$unregistered_element; - - $counts['new_elements']++; - array_push($inserts, $insert); - } - else - { - $output.= ''; - $output.= $lang['update_missing_tn'].' : '.$unregistered_element; - $output.= ' ('; - $output.= $conf['prefix_thumbnail']; - $output.= get_filename_wo_extension($unregistered_element); - $output.= '.XXX'; - $output.= ', XXX = '; - $output.= implode(', ', $conf['picture_ext']); - $output.= ')
'; - } + $insert{'id'} = $next_element_id++; + $insert{'file'} = $filename; + $insert{'storage_category_id'} = $db_fulldirs[$dirname]; + $insert{'date_available'} = CURRENT_DATE; + $insert{'tn_ext'} = $tn_ext; + $insert{'path'} = $path; + + array_push($inserts, $insert); + array_push($insert_links, + array('image_id' => $insert{'id'}, + 'category_id' => $insert{'storage_category_id'})); + array_push($infos, array('path' => $insert{'path'}, + 'info' => $lang['update_research_added'])); } else { - $representative_ext = ''; - foreach ($conf['picture_ext'] as $ext) - { - $candidate = $file_wo_ext.'.'.$ext; - if (!in_array($candidate, $fs_representatives)) - { - continue; - } - else - { - $representative_ext = $ext; - break; - } - } - - $insert = array(); - $insert['file'] = $unregistered_element; - $insert['path'] = $dir.$unregistered_element; - $insert['storage_category_id'] = $category_id; - $insert['date_available'] = CURRENT_DATE; - if ( $tn_ext != '' ) + array_push($errors, array('path' => $path, 'type' => 'PWG-UPDATE-2')); + } + } + else + { + // searching a representative + $representative_ext = ''; + $base_test = $dirname.'/pwg_representative/'.$filename_wo_ext.'.'; + foreach ($conf['picture_ext'] as $ext) + { + $test = $base_test.$ext; + if (isset($fs['representatives'][$test])) { - $insert['tn_ext'] = $tn_ext; + $representative_ext = $ext; + break; } - if ( $representative_ext != '' ) + else { - $insert['representative_ext'] = $representative_ext; + continue; } + } - $counts['new_elements']++; - array_push($inserts, $insert); + $insert{'id'} = $next_element_id++; + $insert{'file'} = $filename; + $insert{'storage_category_id'} = $db_fulldirs[$dirname]; + $insert{'date_available'} = CURRENT_DATE; + $insert{'path'} = $path; + + if ($tn_ext != '') + { + $insert{'tn_ext'} = $tn_ext; } - } - else - { - $output.= '"'; - $output.= $unregistered_element.'" : '; - $output.= $lang['update_wrong_dirname'].'
'; + if ($representative_ext != '') + { + $insert{'representative_ext'} = $representative_ext; + } + + array_push($inserts, $insert); + array_push($insert_links, + array('image_id' => $insert{'id'}, + 'category_id' => $insert{'storage_category_id'})); + array_push($infos, array('path' => $insert{'path'}, + 'info' => $lang['update_research_added'])); } } - + if (count($inserts) > 0) { - // inserts all found pictures - $dbfields = array( - 'file','storage_category_id','date_available','tn_ext' - ,'representative_ext','path' - ); - mass_inserts(IMAGES_TABLE, $dbfields, $inserts); - - // what are the ids of the pictures in the $category_id ? - $ids = array(); - - $query = ' -SELECT id - FROM '.IMAGES_TABLE.' - WHERE storage_category_id = '.$category_id.' -;'; - $result = pwg_query($query); - while ($row = mysql_fetch_array($result)) + if (!$simulate) { - array_push($ids, $row['id']); + // inserts all new elements + $dbfields = array( + 'id','file','storage_category_id','date_available','tn_ext' + ,'representative_ext','path' + ); + mass_inserts(IMAGES_TABLE, $dbfields, $inserts); + + // insert all links between new elements and their storage category + $dbfields = array('image_id','category_id'); + mass_inserts(IMAGE_CATEGORY_TABLE, $dbfields, $insert_links); } + $counts['new_elements'] = count($inserts); + } - // recreation of the links between this storage category pictures and - // its storage category - $query = ' -DELETE FROM '.IMAGE_CATEGORY_TABLE.' - WHERE category_id = '.$category_id.' - AND image_id IN ('.implode(',', $ids).') -;'; - pwg_query($query); - - foreach ($ids as $num => $image_id) + // delete elements that are in database but not in the filesystem + $to_delete_elements = array(); + foreach (array_diff($db_elements, $fs['elements']) as $path) + { + array_push($to_delete_elements, array_search($path, $db_elements)); + array_push($infos, array('path' => $path, + 'info' => $lang['update_research_deleted'])); + } + if (count($to_delete_elements) > 0) + { + if (!$simulate) { - $ids[$num] = '('.$category_id.','.$image_id.')'; + delete_elements($to_delete_elements); } - $query = ' -INSERT INTO '.IMAGE_CATEGORY_TABLE.' - (category_id,image_id) VALUES - '.implode(',', $ids).' -;'; - pwg_query($query); - - set_random_representant(array($category_id)); + $counts['del_elements'] = count($to_delete_elements); } - return $output; + + echo get_elapsed_time($start_files, get_moment()); + echo ' for new method scanning files
'; } // +-----------------------------------------------------------------------+ // | template initialization | // +-----------------------------------------------------------------------+ $template->set_filenames(array('update'=>'admin/update.tpl')); -$base_url = PHPWG_ROOT_PATH.'admin.php?page=update'; +$result_title = ''; +if (isset($simulate) and $simulate) +{ + $result_title.= $lang['update_simulation_title'].' '; +} +$result_title.= $lang['update_part_research']; $template->assign_vars( array( @@ -535,17 +476,21 @@ $template->assign_vars( 'L_UPDATE_SYNC_METADATA_NEW'=>$lang['update_sync_metadata_new'], 'L_UPDATE_SYNC_METADATA_ALL'=>$lang['update_sync_metadata_all'], 'L_UPDATE_CATS_SUBSET'=>$lang['update_cats_subset'], - 'L_RESULT_UPDATE'=>$lang['update_part_research'], + 'L_RESULT_UPDATE'=>$result_title, 'L_NB_NEW_ELEMENTS'=>$lang['update_nb_new_elements'], 'L_NB_NEW_CATEGORIES'=>$lang['update_nb_new_categories'], 'L_NB_DEL_ELEMENTS'=>$lang['update_nb_del_elements'], 'L_NB_DEL_CATEGORIES'=>$lang['update_nb_del_categories'], + 'L_UPDATE_NB_ERRORS'=>$lang['update_nb_errors'], 'L_SEARCH_SUBCATS_INCLUDED'=>$lang['search_subcats_included'], - - 'U_SYNC_DIRS'=>add_session_id($base_url.'&update=dirs'), - 'U_SYNC_ALL'=>add_session_id($base_url.'&update=all'), - 'U_SYNC_METADATA_NEW'=>add_session_id($base_url.'&metadata=all:new'), - 'U_SYNC_METADATA_ALL'=>add_session_id($base_url.'&metadata=all') + 'L_UPDATE_WRONG_DIRNAME_INFO'=>$lang['update_wrong_dirname_info'], + 'L_UPDATE_MISSING_TN_INFO'=>$lang['update_missing_tn_info'], + 'PICTURE_EXT_LIST'=>implode(',', $conf['picture_ext']), + 'L_UPDATE_ERROR_LIST_TITLE'=>$lang['update_error_list_title'], + 'L_UPDATE_ERRORS_CAPTION'=>$lang['update_errors_caption'], + 'L_UPDATE_DISPLAY_INFO'=>$lang['update_display_info'], + 'L_UPDATE_SIMULATE'=>$lang['update_simulate'], + 'L_UPDATE_INFOS_TITLE'=>$lang['update_infos_title'] )); // +-----------------------------------------------------------------------+ // | introduction : choices | @@ -570,42 +515,57 @@ SELECT id,name,uppercats,global_rank else if (isset($_POST['submit']) and ($_POST['sync'] == 'dirs' or $_POST['sync'] == 'files')) { - $counts = array( - 'new_elements' => 0, - 'new_categories' => 0, - 'del_elements' => 0, - 'del_categories' => 0 - ); - - if (isset($_POST['cat'])) - { - $opts['category_id'] = $_POST['cat']; - } - else - { - $opts['category_id'] = 'NULL'; - } - - $start = get_moment(); - $categories = insert_local_category($opts['category_id']); - echo get_elapsed_time($start,get_moment()).' for scanning directories
'; - $template->assign_block_vars( 'update', array( - 'CATEGORIES'=>$categories, 'NB_NEW_CATEGORIES'=>$counts['new_categories'], 'NB_DEL_CATEGORIES'=>$counts['del_categories'], 'NB_NEW_ELEMENTS'=>$counts['new_elements'], - 'NB_DEL_ELEMENTS'=>$counts['del_elements'] + 'NB_DEL_ELEMENTS'=>$counts['del_elements'], + 'NB_ERRORS'=>count($errors), )); - $start = get_moment(); - update_category('all'); - echo get_elapsed_time($start,get_moment()).' for update_category(all)
'; - $start = get_moment(); - ordering(); - update_global_rank(); - echo get_elapsed_time($start, get_moment()).' for ordering categories
'; + + if (count($errors) > 0) + { + $template->assign_block_vars('update.errors', array()); + foreach ($errors as $error) + { + $template->assign_block_vars( + 'update.errors.error', + array( + 'ELEMENT' => $error['path'], + 'LABEL' => $error['type'].' ('.$error_labels[$error['type']].')' + )); + } + } + if (count($infos) > 0 + and isset($_POST['display_info']) + and $_POST['display_info'] == 1) + { + $template->assign_block_vars('update.infos', array()); + foreach ($infos as $info) + { + $template->assign_block_vars( + 'update.infos.info', + array( + 'ELEMENT' => $info['path'], + 'LABEL' => $info['info'] + )); + } + } + + if (!$simulate) + { + $start = get_moment(); + update_category('all'); + echo get_elapsed_time($start,get_moment()); + echo ' for update_category(all)
'; + $start = get_moment(); + ordering(); + update_global_rank(); + echo get_elapsed_time($start, get_moment()); + echo ' for ordering categories
'; + } } // +-----------------------------------------------------------------------+ // | synchronize metadata | -- cgit v1.2.3