/[drupal]/contributions/sandbox/jeremy/4.4.0/kerneltrap/lock.patch
ViewVC logotype

Contents of /contributions/sandbox/jeremy/4.4.0/kerneltrap/lock.patch

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (show annotations) (download) (as text)
Sun Mar 28 04:53:10 2004 UTC (5 years, 7 months ago) by jeremy
Branch: MAIN
CVS Tags: HEAD
File MIME type: text/x-patch
Adds lock.inc, and prevents two admins from editing the same node at the
same time.
1 #
2 # This patch introduces includes/lock.inc, a generic locking mechanism.
3 # It then wraps the administrative editing of nodes in a lock, preventing
4 # more than one administrator from making changes at the same time.
5 #
6 # To use, you will need to add the following database table:
7 #
8 # CREATE TABLE locks (
9 # name varchar(64) NOT NULL default '',
10 # microtime varchar(64) NOT NULL default '',
11 # ttl int NOT NULL default '0',
12 # status tinyint(1) unsigned NOT NULL default '0',
13 # quantity int(10) unsigned NOT NULL default '0',
14 # PRIMARY KEY (name),
15 # KEY (microtime),
16 # KEY (ttl),
17 # KEY (status),
18 # KEY (quantity)
19 # );
20 #
21 # Then apply this patch.
22 #
23 # Finally, with the patch applied and the database updated, enable content
24 # locking at "administer >> configuration >> modules >> node"
25 #
26
27 --- includes/common.inc.orig 2004-03-27 17:05:57.267404839 -0500
28 +++ includes/common.inc 2004-03-27 17:06:37.062143144 -0500
29 @@ -1225,6 +1225,7 @@ function drupal_xml_parser_create(&$data
30 include_once "includes/xmlrpc.inc";
31 include_once "includes/tablesort.inc";
32 include_once "includes/file.inc";
33 +include_once "includes/lock.inc";
34
35 // set error handler:
36 set_error_handler("error_handler");
37 --- modules/node.module.orig 2004-03-27 17:05:57.266404946 -0500
38 +++ modules/node.module 2004-03-27 17:06:37.061143251 -0500
39 @@ -578,6 +578,12 @@ function node_settings() {
40 $output .= form_select(t('Length of trimmed posts'), 'teaser_length', variable_get('teaser_length', 600), array(0 => t('Unlimited'), 200 => t('200 characters'), 400 => t('400 characters'), 600 => t('600 characters'), 800 => t('800 characters'), 1000 => t('1000 characters'), 1200 => t('1200 characters'), 1400 => t('1400 characters'), 1600 => t('1600 characters'), 1800 => t('1800 characters'), 2000 => t('2000 characters')), t("The maximum number of characters used in the trimmed version of a post. Drupal will use this setting to determine at which offset long posts should be trimmed. The trimmed version of a post is typically used as a teaser when displaying the post on the main page, in XML feeds, etc. To disable teasers, set to 'Unlimited'. Note that this setting will only affect new or updated content and will not affect existing teasers."));
41 $output .= form_radios(t('Preview post'), 'node_preview', variable_get('node_preview', 0), array(t('Optional'), t('Required')), t('Must users preview posts before submitting?'));
42
43 + // locking:
44 + // manually formatting time array so the seconds stay seconds...
45 + $duration = array(0 => t("disabled"), 30 => t("30 sec"), 60 => t("1 min"), 120 => t("2 min"), 180 => t("3 min"), 240 => t("4 min"), 300 => t("5 min"), 600 => t("10 min"), 900 => t("15 min"), 1800 => t("30 min"), 3600 => t("1 hour"));
46 + $group = form_select(t("Content locking"), "node_lock", variable_get("node_lock", 0), $duration, t("Content locking allows only one administrator at a time to modify any given piece of content. To enable, select how long an administrator can hold a lock on content. If the administrator takes longer than this amount of time to make changes to content, another administrator is allowed to come along and grab the lock, thereby preventing the first from saving changes."));
47 + $output .= form_group(t("Lock settings"), $group);
48 +
49 return $output;
50 }
51
52 @@ -646,7 +652,76 @@ function node_admin_edit($node) {
53 $output .= implode("\n", module_invoke_all('node_link', $node));
54
55 return $output;
56 +}
57
58 +function node_lock($nid, $lock_type) {
59 + if($lock->ttl = variable_get("node_lock", 0)) {
60 + $name="lock_$nid";
61 +
62 + switch ($lock_type) {
63 + case "lock":
64 + // get the lock and store the key in a session variable
65 + if ($key = lock_lock($name, 0, $lock)) {
66 + $_SESSION[$name] = $key;
67 + }
68 + else {
69 + return 0;
70 + }
71 + break;
72 + case "renew":
73 + if($_SESSION[$name]) {
74 + if($key = lock_renew($name, $_SESSION[$name], $lock)) {
75 + // we still own the lock
76 + $_SESSION[$name] = $key;
77 + }
78 + else {
79 + // we don't own the lock
80 + unset($_SESSION[$name]);
81 + return 0;
82 + }
83 + }
84 + // we can only try and renew if we have a key
85 + else {
86 + return 0;
87 + }
88 + break;
89 + case "edit":
90 + // if we already have a key, try and renew it
91 + if ($_SESSION[$name]) {
92 + // if we fail to renew, the key will be erased
93 + node_lock($nid, "renew");
94 + }
95 + // if we don't have a key, try and get one
96 + if(!$_SESSION[$name] && !node_lock($nid, "lock")) {
97 + return 0;
98 + }
99 + break;
100 + case "preview":
101 + // if we already have a key, try and renew it
102 + if($_SESSION[$name] && !node_lock($nid, "renew")) {
103 + return 0;
104 + }
105 + // if we don't have a key, we shouldn't be here
106 + elseif (!$_SESSION[$name]) {
107 + return 0;
108 + }
109 + break;
110 + case "submit":
111 + case "delete":
112 + // verify that our key is still valid
113 + if ($_SESSION[$name] && !node_lock($nid, "renew")) {
114 + return 0;
115 + }
116 + // display error if we don't have a key
117 + elseif (!$_SESSION[$name]) {
118 + return 0;
119 + }
120 + lock_unlock($name, $_SESSION[$name]);
121 + unset($_SESSION[$name]);
122 + break;
123 + }
124 + }
125 + return 1;
126 }
127
128 function node_admin_nodes() {
129 @@ -947,20 +1022,46 @@ function node_admin() {
130 $output = search_type('node', url('admin/node/search'), $_POST['keys']);
131 break;
132 case 'edit':
133 - $output = node_admin_edit(arg(3));
134 + if(node_lock(arg(3), "edit")) {
135 + $output = node_admin_edit(arg(3));
136 + }
137 + else {
138 + $output = node_display_lock_error("admin edit");
139 + }
140 break;
141 case 'delete':
142 - $output = node_delete(array('nid' => arg(3)));
143 + if(node_lock(arg(3), "delete")) {
144 + $output = node_delete(array('nid' => arg(3)));
145 + }
146 + else {
147 + $output = node_display_lock_error("admin delete");
148 + }
149 break;
150 case t('Preview'):
151 - $edit = node_validate($edit, $error);
152 - $output = node_preview($edit, $error);
153 + if(node_lock($edit['nid'], "preview")) {
154 + $edit = node_validate($edit, $error);
155 + $output = node_preview($edit, $error);
156 + }
157 + else {
158 + $output = node_display_lock_error("admin preview");
159 + }
160 break;
161 case t('Submit'):
162 - $output = node_submit($edit);
163 + if(node_lock($edit['nid'], "submit")) {
164 + $output = node_submit($edit);
165 + }
166 + else {
167 + $output = node_display_lock_error("admin submit");
168 + }
169 break;
170 case t('Delete'):
171 - $output = node_delete($edit);
172 + // we are still previewing during this step, not yet deleting
173 + if(node_lock($edit['nid'], "preview")) {
174 + $output = node_delete($edit);
175 + }
176 + else {
177 + $output = node_display_lock_error("admin delete");
178 + }
179 break;
180 case t('Save configuration'):
181 case t('Reset to defaults'):
182 @@ -973,6 +1074,17 @@ function node_admin() {
183 print theme('page', $output);
184 }
185
186 +function node_display_lock_error($type) {
187 + switch($type) {
188 + case "admin edit":
189 + return t("Temporary access failure. Another administrater is currently making changes to this content. Please try again later.");
190 + case "admin preview":
191 + case "admin submit":
192 + case "admin delete":
193 + return t("Your session has expired. Another administrator is currently making changes to this content. Please try again later.");
194 + }
195 +}
196 +
197 function node_block($op = 'list', $delta = 0) {
198
199 if ($op == 'list') {
200 @@ -1255,7 +1367,7 @@ function node_add($type) {
201 $edit = $_POST['edit'];
202
203 /*
204 - ** If a node type has been specified, validate it existence. If no
205 + ** If a node type has been specified, validate its existence. If no
206 ** (valid) node type has been provided, display a node type overview.
207 */
208
209 --- includes/lock.inc.orig 2004-03-27 17:07:45.005864995 -0500
210 +++ includes/lock.inc 2004-03-27 17:06:37.062143144 -0500
211 @@ -0,0 +1,286 @@
212 +<?php
213 +
214 +/* ***********************************
215 + * external functions (lock API)
216 + * ***********************************/
217 +
218 +/**
219 + * This function allows you to (attempt to) grab a lock. If you succesfully
220 + * grab a lock, you now own it and you can be sure that nobody else can get
221 + * it until you free it (or it times out).
222 + * This function is actually little more than a wrapper, allowing you to easily
223 + * develop your own locking mechanisms if you so require. For example, you
224 + * may wish to implement row-level locking, or file-based locking, neither of
225 + * which is currently provided by this include file.
226 + * The built in mechanisms are quite powerful, however, so the vast majority
227 + * of users will not need to write their own locking mechanisms.
228 + *
229 + * Built in lock types:
230 + * db - generic locking in a single database table
231 + * db_rw - more complex read-many write-one locking (**currently disabled**)
232 + *
233 + * @param string $name the name of the lock
234 + * @param string $block 0 = don't block (return immediately),
235 + * 1 = block (don't return until we have the lock)
236 + * @param object $attributes opptional items used by some types of lock
237 + * @param string $type specify what type of locking mechanism to use
238 + *
239 + * @return int # = key to the lock we own; 0 = we do not own the lock
240 + */
241 +function lock_lock($name, $block = 0, $attributes = NULL, $type = "db") {
242 + $lock = "lock_$type"."_lock";
243 + return $lock($name, $block, $attributes);
244 +}
245 +
246 +/**
247 + * This function allows you to renew a lock if you own it. This is useful for
248 + * lengthy interactive tasks with users. If renew lock returns with a value,
249 + * you know that you still own the lock.
250 + * This function is actually just a wrapper, allowing you to easily use the
251 + * built in locking mechanisms, or even to develop your own. See the comments
252 + * in 'lock_lock()' for more information.
253 + *
254 + * @param string $name the name of the lock
255 + * @param int $key the key for the lock
256 + * @param object $attributes opptional items used by some types of lock
257 + * @param string $type specify what type of locking mechanism to use
258 + *
259 + * @return int # = new key to the renewed lock; 0 = we lost the lock
260 + */
261 +
262 +function lock_renew($name, $key, $attributes = NULL, $type = "db") {
263 + $lock = "lock_$type"."_renew";
264 + return $lock($name, $key, $attributes);
265 +}
266 +
267 +/**
268 + * This function allows you to check the status of a named lock. If the lock
269 + * is held by you are another process, it returns 1. If the lock is not
270 + * held by anyone, it returns 0.
271 + * This function is actually just a wrapper, allowing you to easily use the
272 + * built in locking mechanisms, or even to develop your own. See the comments
273 + * in 'lock_lock()' for more information.
274 + *
275 +
276 + * @param string $name the name of the lock
277 + * @param object $attributes opptional items used by some types of lock
278 + * @param string $type specify what type of locking mechanism to use
279 + *
280 + * @return int 1 = lock is held; 0 = lock is not held
281 + */
282 +function lock_is_locked($name, $attributes = NULL, $type = "db") {
283 + $is_locked = "lock_$type"."_is_locked";
284 + return $is_locked($name, $attributes);
285 +}
286 +
287 +/**
288 + * This function allows you to unlock a lock. If it succeeds in unlocking the
289 + * lock, it returns a 1. If it fails, it returns a 0.
290 + * This function is actually just a wrapper, allowing you to easily use the
291 + * built in locking mechanisms, or even to develop your own. See the comments
292 + * in 'lock_lock()' for more information.
293 + *
294 + * @param string $name the name of the lock
295 + * @param object $attributes opptional items used by some types of lock
296 + * @param string $type specify what type of locking mechanism to use
297 + *
298 + * @return int 1 = succesfully unlocked lock; 0 = failed to unlock lock
299 + */
300 +function lock_unlock($name, $key, $attributes = NULL, $type = "db") {
301 + $unlock = "lock_$type"."_unlock";
302 + return $unlock($name, $key, $attributes);
303 +}
304 +
305 +/* **********************************************
306 + * internal functions (don't call directly)
307 + * **********************************************/
308 +
309 +/**
310 + * This is the default lock type. The locks are stored in a database table.
311 + * You can optionally pass in the following attributes to control locking
312 + * behavior:
313 + * - ttl: after this number of seconds the lock will be automatically
314 + * unlocked without an explicit call to lock_unlock(). This is
315 + * primarily to handle the inconsistent nature of http. Defaults
316 + * to 15 seconds.
317 + * - timeout: if blocking while trying to obtain a lock, you can optionally
318 + * specify in seconds how long before we give up. (otherwise
319 + * we'll try forever until we get the lock)
320 + *
321 + * @param string $name the name of the lock
322 + * @param string $block 1 = block, 0 = don't block
323 + * @param object $attributes optional, defined above
324 + *
325 + * @return int 1 = we own the lock; 0 = we failed to obtain the lock
326 + */
327 +function lock_db_lock($name, $block, $attributes) {
328 + /* Due to the nature of http, it's very possible that someone will grab a lock
329 + ** and then fail to free it. Because of this, we have to specify how long
330 + ** the lock is valid before we determine it to be stale and unlock it.
331 + */
332 + if ($attributes->ttl) {
333 + $ttl = $attributes->ttl;
334 + }
335 + else {
336 + /* We were not explicitly told how long a lock is valid, so we're going to
337 + ** default to a relatively sane value of 15 seconds. Depending on the
338 + ** web application, this could be way too long, or way too short...
339 + */
340 + $ttl = 15;
341 + }
342 + if ($attributes->timeout) {
343 + // blocking attempt to obtain a lock will timeout after this many seconds
344 + $timeout = $attributes->timeout;
345 + }
346 + else {
347 + // we'll never timeout...
348 + $timeout = 0;
349 + }
350 + // initialize the loop counter, used to optionally timeout a lock attempt
351 + $loop = 0;
352 +
353 + // we enter a loop to try and obtain the lock
354 + while (1) {
355 + /* The first step in obtaining a lock is checking to see if it is already
356 + ** held by another process. Even if the lock exists, it may be stale
357 + ** in which case we need to unlock it. In other words, we can't skip this
358 + ** step.
359 + **/
360 + if (!lock_db_is_locked($name, $attributes)) {
361 + /* At this moment, we know that the lock is available, so we're going to
362 + ** try and grab it...
363 + */
364 + $key = microtime();
365 + if(@db_query("INSERT INTO {locks} VALUES('%s', '%s', %d, 1, 1)", $name, $key, $ttl)) {
366 + /* The presence of a return code means that the database did not
367 + ** return an error. In other words, we own the lock.
368 + */
369 + return $key;
370 + }
371 + else {
372 + /* The absence of a return code means the database returned an error.
373 + ** In other words, we failed to get the lock.
374 + */
375 + if (!$block) {
376 + /* We were not told to block, so we need to exit reporting that we
377 + ** failed to grab the lock.
378 + */
379 + return 0;
380 + }
381 + /* If we got here, we failed to grab the lock, but we were told to
382 + ** block so we're going to sleep a moment and try again.
383 + */
384 + }
385 + }
386 + if (!$block) {
387 + /* When checking the lock, we learned someone else already has it. We
388 + ** were not told to block, so we need to exit reporting that we failed
389 + ** to grab the lock.
390 + */
391 + return 0;
392 + }
393 + /* If we got here, we're still trying to grab the lock. Let's sleep for
394 + ** second then we'll loop around and try again.
395 + */
396 + if (($timeout) && ($loop >= $timeout)) {
397 + // We failed to obtain our lock in the allowed time.
398 + return 0;
399 + }
400 + $loop++;
401 + sleep(1);
402 + }
403 +}
404 +
405 +/**
406 + * This is the default db_renew function. If the lock still exists in the
407 + * database, we update it and issue a new key.
408 + *
409 + * @param string $name the name of the lock
410 + * @param int $key the key for the lock
411 + * @param object $attributes optional
412 + *
413 + * @return int # = new key to the lock we own; 0 = we lost the lock
414 + */
415 +function lock_db_renew($name, $key, $attributes) {
416 + if ($attributes->ttl) {
417 + $ttl = $attributes->ttl;
418 + }
419 + else {
420 + $ttl = 15;
421 + }
422 + $newkey = microtime();
423 + db_query("UPDATE {locks} SET microtime = '%s', ttl = %d WHERE name = '%s' and microtime = '%s'", $newkey, $ttl, $name, $key);
424 + // Verify that we actually updated a database row.
425 + if (db_affected_rows() == 1) {
426 + // Great, our lock is still there, and we've successfully renewed it.
427 + return ($newkey);
428 + }
429 + // Our lock is gone, we can't renew it.
430 + return 0;
431 +}
432 +
433 +/**
434 + * This is the default db is_locked function. It checks if the named lock
435 + * is locked or not. It is very unlikely that you will ever need to call this
436 + * function directly.
437 + * You can pass in the following attributes:
438 + *
439 + * @param string $name the name of the lock
440 + * @param object $attributes optional, defined above
441 + *
442 + * @return int 1 = the lock is locked; 0 = the lock is not locked
443 + */
444 +function lock_db_is_locked($name, $attributes) {
445 + // See if the lock exists in the database.
446 + $lock = db_fetch_object(db_query("SELECT status,microtime,ttl FROM {locks} WHERE name = '%s'", $name));
447 + if($lock->status == 1) {
448 + // The lock is in the database, but it may be stale...
449 + list($cur_micro, $cur_sec) = explode(" ", microtime());
450 + list($old_micro, $old_sec) = explode(" ", $lock->microtime);
451 + $old_sec = $old_sec + $lock->ttl;
452 + if (($cur_sec > $old_sec) || ($cur_sec == $old_sec && $cur_micro > $old_micro)) {
453 + /* The lock is stale, meaning that it's been in the database too long.
454 + ** Pass in the unique tampstamp of the lock, and try to unlock it.
455 + */
456 + $key = $lock->microtime;
457 + lock_db_unlock($name, $key, $attributes);
458 + return 0;
459 + }
460 + return 1;
461 + }
462 + // the lock is not currently held
463 + return 0;
464 +}
465 +
466 +/**
467 + * This is the default db unlock function. It will attempt to unlock the named
468 + * lock.
469 + * It does not recognize any attributes at this time.
470 + *
471 + * @param string $name the name of the lock
472 + * @param int $key the key to unlock (creation timestamp)
473 + * @param object $attributes optional, defined above
474 + *
475 + * @return int 1 = the lock was unlocked; 0 = the lock was not unlocked
476 + */
477 +function lock_db_unlock($name, $key, $attributes) {
478 + /* Unlocking a lock is as simple as deleting it. (We want to do that rather
479 + ** than simply changing the status as this logic is built around the theory
480 + ** that you can only have one lock in the table by the same name.)
481 + ** Note: We must delay the deletion of a lock to be sure whatever logic
482 + ** required the lock in the first place is finished. Hence the sleep before
483 + ** the delete.
484 + */
485 + db_query("DELETE FROM {locks} WHERE name = '%s' and microtime = '%s'", $name, $key);
486 + if (db_error()) {
487 + /* Our delete failed. Why? Probably because someone has already freed the
488 + ** lock. In other words, it doesn't matter. But we'll leave it up to
489 + ** whomever is actually using the lock to decide if this matters. We'll
490 + ** return a 0 to say that we failed to unlock the lock. Most likely this
491 + ** will be ignored.
492 + */
493 + return 0;
494 + }
495 + // We succesfully unlocked the lock.
496 + return 1;
497 +}

  ViewVC Help
Powered by ViewVC 1.1.2