- modified etag/optimistic locking, to always increment the etag, was before only via the GUI

- replaced own "locks" via egw_cal.cal_edit_user/time with the new egw_vfs::lock() methods
This commit is contained in:
Ralf Becker 2008-05-07 22:12:25 +00:00
parent 641817662b
commit 5d50d41004
6 changed files with 269 additions and 246 deletions

View File

@ -81,6 +81,7 @@ class bocalupdate extends bocal
*/
function update(&$event,$ignore_conflicts=false,$touch_modified=true,$ignore_acl=false)
{
//error_log(__METHOD__."(".str_replace(array("\n",' '),'',print_r($event,true)).",$ignore_conflicts,$touch_modified,$ignore_acl)");
if ($this->debug > 1 || $this->debug == 'update')
{
$this->debug_message('bocalupdate::update(%1,ignore_conflict=%2,touch_modified=%3,ignore_acl=%4)',
@ -684,6 +685,7 @@ class bocalupdate extends bocal
*/
function save($event)
{
//error_log(__METHOD__."(".str_replace(array("\n",' '),'',print_r($event,true)).",$etag)");
// check if user has the permission to update / create the event
if ($event['id'] && !$this->check_perms(EGW_ACL_EDIT,$event['id']) ||
!$event['id'] && !$this->check_perms(EGW_ACL_EDIT,0,$event['owner']) &&
@ -702,9 +704,6 @@ class bocalupdate extends bocal
// we convert here from user-time to timestamps in server-time!
if (isset($event[$ts])) $event[$ts] = $event[$ts] ? $this->date2ts($event[$ts],true) : 0;
}
// Lock realized with a counter, that is checked and incremented as we save the entry
$check_etag = ($event['etag'] ? $event['etag']:0);
// same with the recur exceptions
if (isset($event['recur_exception']) && is_array($event['recur_exception']))
{
@ -721,7 +720,7 @@ class bocalupdate extends bocal
$event['alarm'][$id]['time'] = $this->date2ts($alarm['time'],true);
}
}
if (($cal_id = $this->so->save($event,$set_recurrences,NULL,$check_etag)) && $set_recurrences && $event['recur_type'] != MCAL_RECUR_NONE)
if (($cal_id = $this->so->save($event,$set_recurrences,NULL,$event['etag'])) && $set_recurrences && $event['recur_type'] != MCAL_RECUR_NONE)
{
$save_event['id'] = $cal_id;
$this->set_recurrences($save_event);
@ -1102,18 +1101,4 @@ class bocalupdate extends bocal
return $cat_list;
}
/**
* updates the edit user information for timelocking an event
*
* @param array &$event2update event-array, on return some values might be changed due to set defaults
* this is a wrapper for the socal function
* cal_edit_time is set to current timestamp
* @return the returnvalue of so->save_edit_user (0 (someone else modified the entry), true (saved) or false (could not save)))
*/
function update_edit_user(&$event2update)
{
$event2update['edit_time']=$this->now_su;
return $this->so->save_edit_user($event2update);
}
}

View File

@ -464,12 +464,13 @@ ORDER BY cal_user_type, cal_usre_id
* @param array $event
* @param boolean &$set_recurrences on return: true if the recurrences need to be written, false otherwise
* @param int $change_since=0 time from which on the repetitions should be changed, default 0=all
* @return boolean/int false on error, cal_id otherwise
* @return int $check_etag check etag from GUI, if there is any Change since the last save SB:Lock for etag
* @param int &$etag etag=null etag to check or null, on return new etag
* @return boolean/int false on error, 0 if etag does not match, cal_id otherwise
*/
function save($event,&$set_recurrences,$change_since=0,$check_modified=0)
function save($event,&$set_recurrences,$change_since=0,&$etag=null)
{
//echo "<p>socal::save(,$change_since) event="; _debug_array($event);
error_log(__METHOD__."(".str_replace(array("\n",' '),'',print_r($event,true)).",$set_recurrences,$change_since,$etag)");
$cal_id = (int) $event['id'];
unset($event['id']);
@ -486,36 +487,21 @@ ORDER BY cal_user_type, cal_usre_id
}
if (is_array($event['cal_category'])) $event['cal_category'] = implode(',',$event['cal_category']);
// while saving handle the etag as condition for the update, to check if an entry was saved before this action occured
$check_etag = ($check_modified ? $check_modified : $event['cal_etag']);
if ($cal_id && $check_etag && $check_modified)
{
//$event2update[]= 'cal_etag=cal_etag+1';
$event2update['cal_etag']= $event['cal_etag']=$check_etag+1;
$event2update['cal_edit_user']= $event['cal_edit_user']=NULL;
$event2update['cal_edit_time']= $event['cal_edit_time']=NULL;
// cal_etag will be set on first save (if not set)
$where = array('cal_id' => $cal_id,'(cal_etag is NULL or cal_etag='.$check_etag.')');
#if ($check_etag) $where['cal_etag'] = $check_etag;
if (!$this->db->update($this->cal_table,$event2update,$where,__LINE__,__FILE__,'calendar'))
{
//error_log("### socal::write(".print_r($event,true).") where=".print_r($where,true)." returning false");
return false; // Error
}
//echo $this->db->affected_rows()."##";
if ($check_etag && $this->db->affected_rows() < 1)
{
//error_log("### socal::write(".print_r($event,true).") where=".print_r($where,true)." returning 0 (nothing updated, eg. condition not met)");
return 0; // someone else updated the modtime or deleted the entry
}
} else {
$event['cal_etag']=($check_etag?$check_etag:1);
}
if ($cal_id)
{
$this->db->update($this->cal_table,$event,array('cal_id' => $cal_id),__LINE__,__FILE__,'calendar');
$where = array('cal_id' => $cal_id);
if (!is_null($etag)) $where['cal_etag'] = $etag;
unset($event['cal_etag']);
$event[] = 'cal_etag=cal_etag+1'; // always update the etag, even if none given to check
$this->db->update($this->cal_table,$event,$where,__LINE__,__FILE__,'calendar');
if (!is_null($etag) && $this->db->affected_rows() < 1)
{
return 0; // wrong etag, someone else updated the entry
}
if (!is_null($etag)) ++$etag;
}
else
{
@ -534,6 +520,7 @@ ORDER BY cal_user_type, cal_usre_id
$event['cal_uid'] = $GLOBALS['egw']->common->generate_uid('calendar',$cal_id);
$this->db->update($this->cal_table,array('cal_uid' => $event['cal_uid']),array('cal_id' => $cal_id),__LINE__,__FILE__,'calendar');
}
$etag = 0;
// new events need to have at least one participant, default to the owner
if (!isset($event['cal_participants']))
{
@ -635,6 +622,10 @@ ORDER BY cal_user_type, cal_usre_id
$this->save_alarm($cal_id,$alarm);
}
}
if (is_null($etag))
{
$etag = $this->db->select($this->cal_table,'cal_etag',array('cal_id' => $cal_id),__LINE__,__FILE__,false,'','calendar')->fetchSingle();
}
return $cal_id;
}
@ -1104,45 +1095,4 @@ ORDER BY cal_user_type, cal_usre_id
),__LINE__,__FILE__,'calendar');
}
}
/**
* Save actually User, who is working on the Calenar Data if there is no user set or the timestamp is "expired"
*
* @param array $event2update
*
* @return (0 (someone else modified the entry), true (saved) or false (could not save)))
*/
function save_edit_user($event2update)
{
$cal_id = (int) $event2update['id'];
//unset($event2update['id']);
// add colum prefix 'cal_' if there's not already a 'recur_' prefix
foreach($event2update as $col => $val)
{
if ($col{0} != '#' && substr($col,0,6) != 'recur_' && $col != 'alarm')
{
$event2update['cal_'.$col] = $val;
unset($event2update[$col]);
}
}
if ($cal_id && $event2update['cal_edit_user'] && $event2update['cal_edit_time'])
{
$locktime = ($GLOBALS['egw_info']['server']['Lock_Time_Calender'] ? $GLOBALS['egw_info']['server']['Lock_Time_Calender'] : 1);
$lockborder=$event2update['cal_edit_time']-$locktime;
$where = array('cal_id' => $cal_id,'(cal_edit_user is NULL or cal_edit_time<'.$lockborder.')');
if (!$this->db->update($this->cal_table,$event2update,$where,__LINE__,__FILE__,'calendar'))
{
//error_log("### socal::write(".print_r($event,true).") where=".print_r($where,true)." returning false");
return false; // Error
}
//echo $this->db->affected_rows()."##";
if ($this->db->affected_rows() < 1)
{
//error_log("### socal::write(".print_r($event,true).") where=".print_r($where,true)." returning 0 (nothing updated, eg. condition not met)");
return 0; // someone else updated the modtime or deleted the entry
}
return true;
}
}
}

View File

@ -514,6 +514,10 @@ class uiforms extends uical
}
if (in_array($button,array('cancel','save','delete')) && $noerror)
{
if ($content['lock_token']) // remove an existing lock
{
egw_vfs::unlock(egw_vfs::app_entry_lock_path('calendar',$content['id']),$content['lock_token'],false);
}
if ($content['no_popup'])
{
$GLOBALS['egw']->redirect_link('/index.php',array(
@ -702,6 +706,39 @@ class uiforms extends uical
}
$view = $preserv['view'] = $preserv['view'] || $event['id'] && !$this->bo->check_perms(EGW_ACL_EDIT,$event);
//echo "view=$view, event="; _debug_array($event);
// shared locking of entries to edit
if (!$view && ($locktime = $GLOBALS['egw_info']['server']['Lock_Time_Calender']) && $event['id'])
{
$lock_path = egw_vfs::app_entry_lock_path('calendar',$event['id']);
$lock_owner = 'mailto:'.$GLOBALS['egw_info']['user']['account_email'];
if (($preserv['lock_token'] = $content['lock_token'])) // already locked --> refresh the lock
{
egw_vfs::lock($lock_path,$preserv['lock_token'],$locktime,$lock_owner,$scope='shared',$type='write',true,false);
}
if (($lock = egw_vfs::checkLock($lock_path)) && $lock['owner'] != $lock_owner)
{
$msg .= ' '.lang('This entry is currently opened by %1!',
(($lock_uid = $GLOBALS['egw']->accounts->name2id(substr($lock['owner'],7),'account_email')) ?
$GLOBALS['egw']->common->grab_owner_name($lock_uid) : $lock['owner']));
}
elseif($lock)
{
$preserv['lock_token'] = $lock['token'];
}
elseif(egw_vfs::lock($lock_path,$preserv['lock_token'],$locktime,$lock_owner,$scope='shared',$type='write',false,false))
{
// install ajax handler to unlock the entry again, if the window get's closed by the user
$GLOBALS['egw']->js->set_onunload("xajax_doXMLHTTP('calendar.uiforms.ajax_unlock',$event[id],'$preserv[lock_token]');");
}
else
{
$msg .= ' '.lang("Can't aquire lock!"); // eg. an exclusive lock via CalDAV ...
$view = true;
}
//echo "<p>lock_path=$lock_path, lock_owner=$lock_owner, lock_token=$preserv[lock_token], msg=$msg</p>\n";
}
$content = array_merge($event,array(
'link_to' => array(
'to_id' => $link_to_id,
@ -710,6 +747,7 @@ class uiforms extends uical
'edit_single' => $preserv['edit_single'], // need to be in content too, as it is used in the template
$this->tabs => $preserv[$this->tabs],
'view' => $view,
'msg' => $msg,
));
$content['duration'] = $content['end'] - $content['start'];
if (isset($this->durations[$content['duration']])) $content['end'] = '';
@ -821,10 +859,6 @@ class uiforms extends uical
}
else
{
if (!is_object($GLOBALS['egw']->js))
{
$GLOBALS['egw']->js = CreateObject('phpgwapi.javascript');
}
// We hide the enddate if one of our predefined durations fits
// the call to set_style_by_class has to be in onload, to make sure the function and the element is already created
$GLOBALS['egw']->js->set_onload("set_style_by_class('table','end_hide','visibility','".($content['duration'] && isset($sel_options['duration'][$content['duration']]) ? 'hidden' : 'visible')."');");
@ -884,25 +918,6 @@ class uiforms extends uical
$content['cancel_needs_refresh'] = (bool)$_GET['cancel_needs_refresh'];
// time locking for entries
$locktime = $GLOBALS['egw_info']['server']['Lock_Time_Calender'];
if ($locktime) {
// the warning and the saving of edit_user and time will only be performed, if there is a lock time set
if (($this->bo->now_su>($event['edit_time']+$locktime)) || ($event['edit_time']==''))
{
//echo "write Lock!!->DB";
$event2update['id']=$event['id'];
$event2update['edit_user']=$this->user;
$event2update['edit_time']=''; // this is set in bo->update_edit_user
$this->bo->update_edit_user($event2update);
}
else
{
if ($event['edit_user'] && $event['edit_user']!=$this->user) $content['msg'].=" ".lang('This entry is opened by user: ').$GLOBALS['egw']->accounts->id2name($event['edit_user']);
}
} else {
}
// non_interactive==true from $_GET calls immediate save action without displaying the edit form
if(isset($_GET['non_interactive']) && (bool)$_GET['non_interactive'] === true)
{
@ -916,6 +931,26 @@ class uiforms extends uical
}
}
/**
* Remove (shared) lock via ajax, when edit popup get's closed
*
* @param int $id
* @param string $token
*/
function ajax_unlock($id,$token)
{
$lock_path = egw_vfs::app_entry_lock_path('calendar',$id);
$lock_owner = 'mailto:'.$GLOBALS['egw_info']['user']['account_email'];
if (($lock = egw_vfs::checkLock($lock_path)) && $lock['owner'] == $lock_owner || $lock['token'] == $token)
{
egw_vfs::unlock($lock_path,$token,false);
}
$response = new xajaxResponse();
$response->addScript('window.close();');
return $response->getXML();
}
/**
* displays a sheduling conflict
*

View File

@ -12,7 +12,7 @@
/* $Id$ */
$setup_info['calendar']['name'] = 'calendar';
$setup_info['calendar']['version'] = '1.5';
$setup_info['calendar']['version'] = '1.5.001';
$setup_info['calendar']['app_order'] = 3;
$setup_info['calendar']['enable'] = 1;
@ -79,3 +79,4 @@

View File

@ -28,9 +28,7 @@
'cal_modifier' => array('type' => 'int','precision' => '4'),
'cal_non_blocking' => array('type' => 'int','precision' => '2','default' => '0'),
'cal_special' => array('type' => 'int','precision' => '2','default' => '0'),
'cal_etag' => array('type' => 'int','precision' => '4'),
'cal_edit_user' => array('type' => 'int','precision' => '4'),
'cal_edit_time' => array('type' => 'int','precision' => '8')
'cal_etag' => array('type' => 'int','precision' => '4','default' => '0')
),
'pk' => array('cal_id'),
'fk' => array(),

View File

@ -1532,17 +1532,71 @@
{
$GLOBALS['egw_setup']->oProc->AddColumn('egw_cal','cal_etag',array(
'type' => 'int',
'precision' => '4'
));
$GLOBALS['egw_setup']->oProc->AddColumn('egw_cal','cal_edit_user',array(
'type' => 'int',
'precision' => '4'
));
$GLOBALS['egw_setup']->oProc->AddColumn('egw_cal','cal_edit_time',array(
'type' => 'int',
'precision' => '8'
'precision' => '4',
'default' => '0'
));
// as we no longer create cal_edit_time|user and already set default 0 for cal_etag, we skip the 1.5 update
return $GLOBALS['setup_info']['calendar']['currentver'] = '1.5.001';
}
return $GLOBALS['setup_info']['calendar']['currentver'] = '1.5';
$test[] = '1.5';
function calendar_upgrade1_5()
{
$GLOBALS['egw_setup']->oProc->DropColumn('egw_cal',array(
'fd' => array(
'cal_id' => array('type' => 'auto','nullable' => False),
'cal_uid' => array('type' => 'varchar','precision' => '255','nullable' => False),
'cal_owner' => array('type' => 'int','precision' => '4','nullable' => False),
'cal_category' => array('type' => 'varchar','precision' => '30'),
'cal_modified' => array('type' => 'int','precision' => '8'),
'cal_priority' => array('type' => 'int','precision' => '2','nullable' => False,'default' => '2'),
'cal_public' => array('type' => 'int','precision' => '2','nullable' => False,'default' => '1'),
'cal_title' => array('type' => 'varchar','precision' => '255','nullable' => False,'default' => '1'),
'cal_description' => array('type' => 'text'),
'cal_location' => array('type' => 'varchar','precision' => '255'),
'cal_reference' => array('type' => 'int','precision' => '4','nullable' => False,'default' => '0'),
'cal_modifier' => array('type' => 'int','precision' => '4'),
'cal_non_blocking' => array('type' => 'int','precision' => '2','default' => '0'),
'cal_special' => array('type' => 'int','precision' => '2','default' => '0'),
'cal_etag' => array('type' => 'int','precision' => '4'),
'cal_edit_time' => array('type' => 'int','precision' => '8')
),
'pk' => array('cal_id'),
'fk' => array(),
'ix' => array(),
'uc' => array()
),'cal_edit_user');
$GLOBALS['egw_setup']->oProc->DropColumn('egw_cal',array(
'fd' => array(
'cal_id' => array('type' => 'auto','nullable' => False),
'cal_uid' => array('type' => 'varchar','precision' => '255','nullable' => False),
'cal_owner' => array('type' => 'int','precision' => '4','nullable' => False),
'cal_category' => array('type' => 'varchar','precision' => '30'),
'cal_modified' => array('type' => 'int','precision' => '8'),
'cal_priority' => array('type' => 'int','precision' => '2','nullable' => False,'default' => '2'),
'cal_public' => array('type' => 'int','precision' => '2','nullable' => False,'default' => '1'),
'cal_title' => array('type' => 'varchar','precision' => '255','nullable' => False,'default' => '1'),
'cal_description' => array('type' => 'text'),
'cal_location' => array('type' => 'varchar','precision' => '255'),
'cal_reference' => array('type' => 'int','precision' => '4','nullable' => False,'default' => '0'),
'cal_modifier' => array('type' => 'int','precision' => '4'),
'cal_non_blocking' => array('type' => 'int','precision' => '2','default' => '0'),
'cal_special' => array('type' => 'int','precision' => '2','default' => '0'),
'cal_etag' => array('type' => 'int','precision' => '4')
),
'pk' => array('cal_id'),
'fk' => array(),
'ix' => array(),
'uc' => array()
),'cal_edit_time');
$GLOBALS['egw_setup']->oProc->AlterColumn('egw_cal','cal_etag',array(
'type' => 'int',
'precision' => '4',
'default' => '0'
));
$GLOBALS['egw_setup']->db->query('UPDATE egw_cal SET cal_etag=0 WHERE cal_etag IS NULL',__LINE__,__FILE__);
return $GLOBALS['setup_info']['calendar']['currentver'] = '1.5.001';
}
?>